Debugger Visualizers
Moderator: 2ffat
Debugger Visualizers
Has anyone successfuly created a c++ visualizer? I have been unable to get the source from here https://forums.embarcadero.com/thread.j ... 2&tstart=0 to work.
My goal is to get a visualizer for stl vectors (similiar to the delphi pascal example with TStrings) developers. I appreciate the help.
Thanks,
Allen
My goal is to get a visualizer for stl vectors (similiar to the delphi pascal example with TStrings) developers. I appreciate the help.
Thanks,
Allen
Re: Debugger Visualizers
What problems are you having with the C++ code I posted in that discussion?aknapple wrote:Has anyone successfuly created a c++ visualizer? I have been unable to get the source from here https://forums.embarcadero.com/thread.j ... 2&tstart=0 to work.
You will not be able to (easily) create a single Visualizer that handles all std::vector instances generically, but you should be able to implement a Visualizer that supports specific std::vector<X> types.
Remy Lebeau (TeamB)
Lebeau Software
Lebeau Software
Re: Debugger Visualizers
Hi Remy,
Creating the visualizer with specifics types is fine. Besides a few syntax errors the this line does not work for sure
StdStringVis = (IOTADebuggerVisualizer *) new TStdStringTimeVisualizer();
it is inside the void __fastcall PACKAGE Register() function. I'm getting an
[BCC32 Error] AnsiStringVisualizer3.cpp(177): E2352 Cannot create instance of abstract class 'TStdStringTimeVisualizer' and
[BCC32 Error] AnsiStringVisualizer3.cpp(177): E2353 Class 'TStdStringTimeVisualizer' is abstract because of '__stdcall Unknown::QueryInterface(const _GUID &,void * *) = 0'
I haven't been able to fully understand what is going on here. I'll keep looking into it. Thanks for the help.
Allen
Creating the visualizer with specifics types is fine. Besides a few syntax errors the this line does not work for sure
StdStringVis = (IOTADebuggerVisualizer *) new TStdStringTimeVisualizer();
it is inside the void __fastcall PACKAGE Register() function. I'm getting an
[BCC32 Error] AnsiStringVisualizer3.cpp(177): E2352 Cannot create instance of abstract class 'TStdStringTimeVisualizer' and
[BCC32 Error] AnsiStringVisualizer3.cpp(177): E2353 Class 'TStdStringTimeVisualizer' is abstract because of '__stdcall Unknown::QueryInterface(const _GUID &,void * *) = 0'
I haven't been able to fully understand what is going on here. I'll keep looking into it. Thanks for the help.
Allen
Re: Debugger Visualizers
Here is another link that might help. http://www.flashtorque.com.au/pubfiles/ ... alizer.cpp
It is not a complete job but I have been try to work with it as well.
Allen
It is not a complete job but I have been try to work with it as well.
Allen
Re: Debugger Visualizers
The VCL's implementation of IInterface (Delphi's version of IUnknown) differs from C++'s actual IUnknown interface, so you have to implement the IUnknown methods manually and have them delegate to TInterfacedObject, eg:aknapple wrote:Besides a few syntax errors the this line does not work for sure
it is inside the void __fastcall PACKAGE Register() function. I'm getting anCode: Select all
StdStringVis = (IOTADebuggerVisualizer *) new TStdStringTimeVisualizer();
[BCC32 Error] AnsiStringVisualizer3.cpp(177): E2352 Cannot create instance of abstract class 'TStdStringTimeVisualizer' and
[BCC32 Error] AnsiStringVisualizer3.cpp(177): E2353 Class 'TStdStringTimeVisualizer' is abstract because of '__stdcall Unknown::QueryInterface(const _GUID &,void * *) = 0'
Code: Select all
class TStdStringTimeVisualizer :
public TInterfacedObject,
...
{
...
public:
// IUnknown
HRESULT __stdcall QueryInterface(const REFIID riid, void** ppv)
{
return TInterfacedObject::QueryInterface(riid, (void*)ppv);
}
ULONG __stdcall AddRef()
{
return TInterfacedObject::_AddRef();
}
HRESULT __stdcall Release()
{
return TInterfacedObject::_Release();
}
...
};
Remy Lebeau (TeamB)
Lebeau Software
Lebeau Software
Re: Debugger Visualizers
Code: Select all
#include <vcl.h>
#pragma hdrstop
#include <Classes.hpp>
#include <Forms.hpp>
#include <SysUtils.hpp>
#include <ToolsAPI.hpp>
#pragma package(smart_init)
const String sStdStringVisualizerName = "std::string and std::wstring Visualizer for C++";
const String sStdStringVisualizerDescription = "Displays the actual string value for std::string and std::wstring instances";
class TStdStringTimeVisualizer :
public TInterfacedObject,
public IOTADebuggerVisualizer,
public IOTADebuggerVisualizerValueReplacer,
public IOTAThreadNotifier
{
private:
int FNotifierIndex;
bool FCompleted;
String FDeferredResult;
public:
// IOTADebuggerVisualizer
int __fastcall GetSupportedTypeCount();
void __fastcall GetSupportedType(int Index, String &TypeName, bool &AllDescendants);
String __fastcall GetVisualizerIdentifier();
String __fastcall GetVisualizerName();
String __fastcall GetVisualizerDescription();
// IOTADebuggerVisualizerValueReplacer
String __fastcall GetReplacementValue(const String Expression, const String TypeName, const String EvalResult);
// IOTAThreadNotifier
void __fastcall EvaluteComplete(const String ExprStr, const String ResultStr, bool CanModify, Cardinal ResultAddress, Cardinal ResultSize, int ReturnCode);
void __fastcall ModifyComplete(const String ExprStr, const String ResultStr, int ReturnCode);
void __fastcall ThreadNotify(TOTANotifyReason Reason);
void __fastcall AfterSave();
void __fastcall BeforeSave();
void __fastcall Destroyed();
void __fastcall Modified();
// IUnknown
HRESULT __stdcall QueryInterface(REFIID riid, void** ppv)
{
return TInterfacedObject::QueryInterface(riid, (void*)ppv);
}
ULONG __stdcall AddRef()
{
return TInterfacedObject::_AddRef();
}
ULONG __stdcall Release()
{
return TInterfacedObject::_Release();
}
};
const String StdStringVisualizerTypes[2] =
{
"std::basic_string<char, std::char_traits<char>, std::allocator<char> >",
"std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> >"
};
// TStdStringTimeVisualizer
void __fastcall TStdStringTimeVisualizer::AfterSave()
{
// don't care about this notification
}
void __fastcall TStdStringTimeVisualizer::BeforeSave()
{
// don't care about this notification
}
void __fastcall TStdStringTimeVisualizer::Destroyed()
{
// don't care about this notification
}
void __fastcall TStdStringTimeVisualizer::Modified()
{
// don't care about this notification
}
void __fastcall TStdStringTimeVisualizer::ModifyComplete(const String ExprStr, const String ResultStr, int ReturnCode)
{
// don't care about this notification
}
void __fastcall TStdStringTimeVisualizer::EvaluteComplete(const String ExprStr, const String ResultStr, bool CanModify, Cardinal ResultAddress, Cardinal ResultSize, int ReturnCode)
{
FCompleted = true;
if( ReturnCode == 0 )
FDeferredResult = ResultStr;
}
void __fastcall TStdStringTimeVisualizer::ThreadNotify(TOTANotifyReason Reason)
{
// don't care about this notification
}
String __fastcall TStdStringTimeVisualizer::GetReplacementValue(const String Expression, const String TypeName, const String EvalResult)
{
_di_IOTAProcess CurProcess;
_di_IOTAThread CurThread;
Char ResultStr[4096];
bool CanModify;
LongWord ResultAddr, ResultSize, ResultVal;
TOTAEvaluateResult EvalRes;
_di_IOTADebuggerServices DebugSvcs;
String Result = EvalResult;
if( ::Supports(BorlandIDEServices, IID_IOTADebuggerServices, (void*)&DebugSvcs) )
CurProcess = DebugSvcs->CurrentProcess;
if( CurProcess )
{
CurThread = CurProcess->CurrentThread;
if( CurThread )
{
EvalRes = CurThread->Evaluate(Expression + "._Myptr()", ResultStr, 4096, CanModify, eseAll, "", ResultAddr, ResultSize, ResultVal, "", 0);
switch( EvalRes )
{
case erOK:
{
Result = ResultStr;
break;
}
case erDeferred:
{
FCompleted = false;
FDeferredResult = "";
FNotifierIndex = CurThread->AddNotifier(this);
while( !FCompleted )
DebugSvcs->ProcessDebugEvents();
CurThread->RemoveNotifier(FNotifierIndex);
FNotifierIndex = -1;
if( FDeferredResult != "" )
Result = FDeferredResult;
else
Result = EvalResult;
break;
}
case erBusy:
{
DebugSvcs->ProcessDebugEvents();
Result = GetReplacementValue(Expression, TypeName, EvalResult);
break;
}
}
}
}
return Result;
}
int __fastcall TStdStringTimeVisualizer::GetSupportedTypeCount()
{
return 2;
}
void __fastcall TStdStringTimeVisualizer::GetSupportedType(int Index, String &TypeName, bool &AllDescendants)
{
AllDescendants = false;
TypeName = StdStringVisualizerTypes[Index];
}
String __fastcall TStdStringTimeVisualizer::GetVisualizerDescription()
{
return sStdStringVisualizerDescription;
}
String __fastcall TStdStringTimeVisualizer::GetVisualizerIdentifier()
{
return ClassName();
}
String __fastcall TStdStringTimeVisualizer::GetVisualizerName()
{
return sStdStringVisualizerName;
}
_di_IOTADebuggerVisualizer StdStringVis;
namespace Stdstringvisualizer
{
void __fastcall PACKAGE Register()
{
StdStringVis = (IOTADebuggerVisualizer *) new TStdStringTimeVisualizer();
_di_IOTADebuggerServices DebuggerServices;
if( ::Supports(BorlandIDEServices, IID_IOTADebuggerServices, (void*)&DebuggerServices) )
DebuggerServices->RegisterDebugVisualizer(StdStringVis);
}
}
void RemoveVisualizer()
{
_di_IOTADebuggerServices DebuggerServices;
if( ::Supports(BorlandIDEServices, IID_IOTADebuggerServices, (void*)&DebuggerServices) )
DebuggerServices->UnregisterDebugVisualizer(StdStringVis);
StdStringVis = NULL;
}
#pragma exit RemoveVisualizer
Here is the exact code. I had to make __stdcall Release() return a ULONG because HRESULT didn't work. With this source I get an [BCC32 Error] StdStringTimeVisualizer.cpp(193): E2031 Cannot cast from 'TStdStringTimeVisualizer *' to 'IOTADebuggerVisualizer *' for this line
StdStringVis = (IOTADebuggerVisualizer *) new TStdStringTimeVisualizer();
My understanding is you can only have IOTADebuggerVisualizer or IOTADebuggerVisualizerValueReplacer. You can not have both. When I remove one or the other the package builds fine and installs. However the visualizer does not register (It is not listed in Tools->Options->Debugger Options->Visualizers). The package does show up as being installed.
If tried several things and still have not been able to get a c++ visualizer to register. I can get delphi's to work just fine. I hope you might have another idea.
Thanks,
Allen
Re: Debugger Visualizers
For what it is worth I was able to get the control to register in C++ Builder with #pragma startup Register. I had to rip out the namespace stuff too.
With all the lack of resources (documentation) and examples on this subject I don't know whether or not I am headed in the right direction. I'm still giving it a shot...
With all the lack of resources (documentation) and examples on this subject I don't know whether or not I am headed in the right direction. I'm still giving it a shot...
Re: Debugger Visualizers
Sorry, my bad. Too much copy/pasting of code.aknapple wrote:Here is the exact code. I had to make __stdcall Release() return a ULONG because HRESULT didn't work.
What I posted is a direct translation of the original Delphi-written visualizer, which does use both interfaces together. It is allowed for multiple ancestors to share common ancestor (otherwise multiple interfaces would not work at all, since all interfaces ultimately derive from IUnknown/IInterface). But since IOTADebuggerVisualizerValueReplacer derives from IOTADebuggerVisualizer, there is no harm in omitting IOTADebuggerVisualizer.aknapple wrote:My understanding is you can only have IOTADebuggerVisualizer or IOTADebuggerVisualizerValueReplacer. You can not have both.
Did you check to make sure that Register() and DebuggerServices->RegisterDebugVisualizer() are actually being called?aknapple wrote:When I remove one or the other the package builds fine and installs. However the visualizer does not register (It is not listed in Tools->Options->Debugger Options->Visualizers). The package does show up as being installed.
I copy/pasted your code into a new package, and it installs and registers OK for me in CB2010. I see it appear in the list of registered visualizers (I tweaked the sStdStringVisualizerName value to differentiate it from Embarcadero's example visualizer), and it functions at run-time as expected. I can even toggle the two visualizers on/off dynamically while the debugger is running (they cannot both be enabled at the same time since they visualize the same data types) and see the same output when either visualizer is enabled, and no formatted output when both are disabled, so I know the C++ written visualizer is working correctly.aknapple wrote:If tried several things and still have not been able to get a c++ visualizer to register.
The ONLY other change I made to your code was to rename the namepace that Register() resides in (and consequently the filename, since they have to match) so it does not conflict with Embarcadero's original std::string visualizer.
Remy Lebeau (TeamB)
Lebeau Software
Lebeau Software
Re: Debugger Visualizers
Then you did not set up the "namespace stuff" correctly to begin with. It MUST match the standard rules for any component package registration:aknapple wrote:For what it is worth I was able to get the control to register in C++ Builder with #pragma startup Register. I had to rip out the namespace stuff too.
- the namespace MUST match the containing filename with the file extension removed.
- the first letter MUST be capitalized, and all other letters MUST be lowercased.
- Register() is case-sensitive and MUST be named "Register".
Remy Lebeau (TeamB)
Lebeau Software
Lebeau Software
Re: Debugger Visualizers
Hi Remy,
Thank you very much for the help. I appreciate you running it in your environment. I am getting this visualizer figured out. I have it up any running. How about the IOTAThread::Evaluate call? Does it operate exactly the same as the Evalute/Modify dialog in the IDE?
I'm trying to figure out why it is tagging on the ".Myptr()" during the evaluate. Ex. inside the GetReplacementValue
EvalRes = CurThread->Evaluate(Expression + "._Myptr()", ResultStr, 4096, CanModify, eseAll, "", ResultAddr, ResultSize, ResultVal, "", 0);
I don't know where the .Myptr() is coming from. TDateTime looks to be using .Val and AnsiString looks to be using .Data. Are these being added by the debugger? Specifically, I'm needing to know what I would use for a std::vector and maybe a std::list if they are any different.
Regards,
Allen
Thank you very much for the help. I appreciate you running it in your environment. I am getting this visualizer figured out. I have it up any running. How about the IOTAThread::Evaluate call? Does it operate exactly the same as the Evalute/Modify dialog in the IDE?
I'm trying to figure out why it is tagging on the ".Myptr()" during the evaluate. Ex. inside the GetReplacementValue
EvalRes = CurThread->Evaluate(Expression + "._Myptr()", ResultStr, 4096, CanModify, eseAll, "", ResultAddr, ResultSize, ResultVal, "", 0);
I don't know where the .Myptr() is coming from. TDateTime looks to be using .Val and AnsiString looks to be using .Data. Are these being added by the debugger? Specifically, I'm needing to know what I would use for a std::vector and maybe a std::list if they are any different.
Regards,
Allen
Re: Debugger Visualizers
Pretty much, yes.aknapple wrote:How about the IOTAThread::Evaluate call? Does it operate exactly the same as the Evalute/Modify dialog in the IDE?
That is the actual member of the std::string and std::wstring classes that accesses the character data. The input Expression is the expression the debugger used to reach the string instance being inspected. Thus, given this example code:aknapple wrote:I'm trying to figure out why it is tagging on the ".Myptr()" during the evaluate. Ex. inside the GetReplacementValue
EvalRes = CurThread->Evaluate(Expression + "._Myptr()", ResultStr, 4096, CanModify, eseAll, "", ResultAddr, ResultSize, ResultVal, "", 0);
I don't know where the .Myptr() is coming from.
Code: Select all
std::string str;
Yes to both. The TDateTime class in C++ has a Val data member (see systdate.h):aknapple wrote:TDateTime looks to be using .Val and AnsiString looks to be using .Data.
Code: Select all
namespace System
{
class RTL_DELPHIRETURN TDateTimeBase
{
public:
double Val;
};
class RTL_DELPHIRETURN TDateTime : public TDateTimeBase
{
...
Code: Select all
namespace System
{
...
class RTL_DELPHIRETURN AnsiStringBase
{
...
protected:
char *Data;
};
template <unsigned short CP>
class RTL_DELPHIRETURN AnsiStringT : public AnsiStringBase
{
...
Code: Select all
typedef AnsiStringT<0> AnsiString;
No. They are in the class declarations.aknapple wrote:Are these being added by the debugger?
They are. Since they are both templated classes, you have to specify to the visualizer which exact specializations you are interested in. For example's sake, let's assume you want to visualize std::vector<int> and std::list<int>. The fully qualified typenames that the visualizer's GetSupportedType() method would return are "std::vector<int, std::allocator<int> >" and "std::list<int, std::allocator<int> >", respectively, and the Evaluate() expression would be like Expression+"._Myfirst" for both.aknapple wrote:I'm needing to know what I would use for a std::vector and maybe a std::list if they are any different.
The easiest way to determine the Expressions to pass to Evaluate() is to simply use the standard Debug Inspectors first and look at the captions they display. The format of the caption is "[expression]: [datatype] :[address]".
Since std::vector and std::list can both contain multiple values, I would suggest you look at Embarcadero's example TStringList visualizer as a base for writing your own. It implements the IOTADebuggerVisualizerExternalViewerUpdater interface instead of the IOTADebuggerVisualizerValueReplacer interface. IOTADebuggerVisualizerExternalViewerUpdater displays its own popup UI to display the inspected values however it wants (like a grid for lists), whereas the IOTADebuggerVisualizerValueReplacer interface is meant for single values (like individual strings) that can be displayed inlined in the various Debug Inspectors.
Remy Lebeau (TeamB)
Lebeau Software
Lebeau Software
Re: Debugger Visualizers
The info in here is excellent and I have managed to implement a visualizer for one of our internal classes.
The one question I have is that the code for erBusy smells and could potentially result in a blown stack because of the recursion.
I haven't managed to crash the IDE yet, but I don't want to add to its instability... Is this something I should worry about?
Thanks in advance.
The one question I have is that the code for erBusy smells and could potentially result in a blown stack because of the recursion.
I haven't managed to crash the IDE yet, but I don't want to add to its instability... Is this something I should worry about?
Thanks in advance.
-
- Active Poster
- Posts: 10
- Joined: Tue Jan 21, 2014 11:08 pm
Re: Debugger Visualizers
umm, bro, the link is broken or not found. I guess, it has been moved to some place else. Can you repost the original information. I could use a little help here.
Re: Debugger Visualizers
Actually, no. A few years ago, there was a server crash that lost a lot of data. The original page is just plain gone.corbingravely wrote:umm, bro, the link is broken or not found. I guess, it has been moved to some place else.
However, there are some discussions archived on CodeNewsFast, for instance:
C++ Debugger Visualizer? (in particular, this post)
Also, aknapple's link to AnsiStringVisualizer.cpp earlier in this discussion thread is still valid, too.
Also, just to make an update to an earlier comment I made in this discussion thread:
In RAD Studio 10.2.1 onward, you can now register visualizers for Delphi Generic types and C+++ template types:You will not be able to (easily) create a single Visualizer that handles all std::vector instances generically, but you should be able to implement a Visualizer that supports specific std::vector<X> types.
New in 10.2.1: Debug visualisers for Delphi generics
Code snippet: IDE debug visualizer plugin for generic and template types
With what exactly?corbingravely wrote:I could use a little help here.
Remy Lebeau (TeamB)
Lebeau Software
Lebeau Software