Debugger Visualizers

This is the forum for miscellaneous technical/programming questions.

Moderator: 2ffat

Debugger Visualizers

Postby aknapple » Sat Sep 11, 2010 1:11 pm

Has anyone successfuly created a c++ visualizer? I have been unable to get the source from here https://forums.embarcadero.com/thread.jspa?messageID=278312&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
aknapple
 
Posts: 8
Joined: Sun May 02, 2010 6:44 pm

Re: Debugger Visualizers

Postby rlebeau » Sun Sep 12, 2010 12:41 am

aknapple wrote:Has anyone successfuly created a c++ visualizer? I have been unable to get the source from here https://forums.embarcadero.com/thread.jspa?messageID=278312&tstart=0 to work.


What problems are you having with the C++ code I posted in that discussion?

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
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1597
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: Debugger Visualizers

Postby aknapple » Mon Sep 13, 2010 11:10 am

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
aknapple
 
Posts: 8
Joined: Sun May 02, 2010 6:44 pm

Re: Debugger Visualizers

Postby aknapple » Mon Sep 13, 2010 11:30 am

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
aknapple
 
Posts: 8
Joined: Sun May 02, 2010 6:44 pm

Re: Debugger Visualizers

Postby rlebeau » Mon Sep 13, 2010 1:42 pm

aknapple wrote:Besides a few syntax errors the this line does not work for sure

Code: Select all
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'


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:

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
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1597
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: Debugger Visualizers

Postby aknapple » Tue Sep 14, 2010 3:37 pm

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


Hi Remy,

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
aknapple
 
Posts: 8
Joined: Sun May 02, 2010 6:44 pm

Re: Debugger Visualizers

Postby aknapple » Wed Sep 15, 2010 12:30 pm

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...
aknapple
 
Posts: 8
Joined: Sun May 02, 2010 6:44 pm

Re: Debugger Visualizers

Postby rlebeau » Wed Sep 15, 2010 5:45 pm

aknapple wrote:Here is the exact code. I had to make __stdcall Release() return a ULONG because HRESULT didn't work.


Sorry, my bad. Too much copy/pasting of code.

aknapple wrote:My understanding is you can only have IOTADebuggerVisualizer or IOTADebuggerVisualizerValueReplacer. You can not have both.


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: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.


Did you check to make sure that Register() and DebuggerServices->RegisterDebugVisualizer() are actually being called?

aknapple wrote:If tried several things and still have not been able to get a c++ visualizer to register.


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.

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
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1597
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: Debugger Visualizers

Postby rlebeau » Wed Sep 15, 2010 5:52 pm

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.


Then you did not set up the "namespace stuff" correctly to begin with. It MUST match the standard rules for any component package registration:

  • 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".

This way, the Register() function is exported using decoration and name-mangling that the IDE is expecting when it checks for exported Register() functions to call when the package is loaded. So, for example, in the original code, the namespace was "Stdstringvisualizer", so the filename had to be "StdStringVisualizer.cpp" (or any other variation of casing). In my test, I renamed the file to "MyStdStringVisualizer.cpp", so the namespace has to be "Mystdstringvisualizer".
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1597
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: Debugger Visualizers

Postby aknapple » Thu Sep 16, 2010 3:03 pm

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
aknapple
 
Posts: 8
Joined: Sun May 02, 2010 6:44 pm

Re: Debugger Visualizers

Postby rlebeau » Thu Sep 16, 2010 4:35 pm

aknapple wrote:How about the IOTAThread::Evaluate call? Does it operate exactly the same as the Evalute/Modify dialog in the IDE?


Pretty much, yes.

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.


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:

Code: Select all
std::string str;


Inspecting te variable causes the Expression to be "str" and thus the visualizer to pass "str._Myptr()" to Evaluate(). The evaluator calls the _Myptr() method to get the char* pointer and copies the (null-terminted) text into the provided buffer.

aknapple wrote:TDateTime looks to be using .Val and AnsiString looks to be using .Data.


Yes to both. The TDateTime class in C++ has a Val data member (see systdate.h):

Code: Select all
namespace System
{
  class RTL_DELPHIRETURN TDateTimeBase
  {
  public:
    double Val;
  };

  class RTL_DELPHIRETURN TDateTime : public TDateTimeBase
  {
  ...


And the AnsiString class in C++ has a Data data member (see dstring.h and sysmac.h):

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;


aknapple wrote:Are these being added by the debugger?


No. They are in the class declarations.

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.


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.

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
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1597
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA

Re: Debugger Visualizers

Postby kiwirob » Tue Apr 29, 2014 11:34 pm

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.
kiwirob
 
Posts: 1
Joined: Tue Apr 29, 2014 11:28 pm

Re: Debugger Visualizers

Postby corbingravely » Wed Dec 12, 2018 9:26 am

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.
corbingravely
Active Poster
Active Poster
 
Posts: 10
Joined: Tue Jan 21, 2014 11:08 pm

Re: Debugger Visualizers

Postby rlebeau » Wed Dec 12, 2018 1:03 pm

corbingravely wrote:umm, bro, the link is broken or not found. I guess, it has been moved to some place else.


Actually, no. A few years ago, there was a server crash that lost a lot of data. The original page is just plain gone.

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:

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.


In RAD Studio 10.2.1 onward, you can now register visualizers for Delphi Generic types and C+++ template types:

New in 10.2.1: Debug visualisers for Delphi generics

Code snippet: IDE debug visualizer plugin for generic and template types

corbingravely wrote:I could use a little help here.


With what exactly?
Remy Lebeau (TeamB)
Lebeau Software
User avatar
rlebeau
BCBJ Author
BCBJ Author
 
Posts: 1597
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA


Return to Technical

Who is online

Users browsing this forum: No registered users and 17 guests