Delphi 2010 Feature Highlight - Debugger Visualizers

By: Tim DelChiaro

Abstract: Free article from the December 2009 issue of Blaise Pascal Magazine

Delphi 2010 Feature Highlight - Debugger Visualizers

by Jeremy North

Reprinted from the December 2009 issue of Blaise Pascal Magazine. Subscribe to Blaise Pascal Magazine to get more great technical articles like this one. If you’re a Delphi, C++Builder, Delphi Prism or RAD Studio 2010 owner, go here to download a special issue and receive a free one year electronic subscription.

This article describes the new Debugger Visualizers feature in Delphi 2010 as well as including the details required to help create your own custom visualizer.  

    What are Debugger Visualizers

Debugger Visualizers allow the data shown by the debugger for a particular type to be represented in a different format. For example the TDateTime type when viewed in the Watch List or Evaluator Tooltip hint appears as a floating point value. In most cases this is not helpful when debugging code. This is where Debugger Visualizers help, by displaying TDateTime (also works for TDate and TTime types) in an easy to read format.

So instead of seeing a value of ‘40105.900806’ you see ‘19/10/2009 9:37:09 PM’.

    Types of Debugger Visualizers

There are two different types of debugger visualizers. The most basic is the Value-Replacer. This visualizer just replaces the string seen in the particular debug instance for the expression. A limitation of the Value-Replacer visualizer is that only one can be registered at a time.

A more complex visualizer is the External-Viewer visualizer. External-Viewer visualizers allow the user to invoke an external window to display more information or enhanced GUI for the selected type. There is no limit to the number of External-Viewer visualizers that can be registered for a type.

    Included Visualizers

Delphi 2010 ships with two Debugger Visualizers available for immediate use (they are already active by default).

The TDateTime visualizer displays the formatted value in place. This means the date and time are displayed where the float value would have been. There are no additional actions required to invoke the visualizer. The TDateTime visualizer is an example of a Value-Replacer debugger visualizer.

The TStrings visualizer is an example of an External-Viewer visualizer. For this visualizer the viewer displays a dockable window that displays the contents of the TStrings variable.

    Invoking External-Viewer visualizers

Visualizers that have an external viewer are displayed slightly different in the IDE to show that the variable can be viewed with an external Visualizer.

The following examples show the user interface for how an external visualizer is identified for Watch List and Evaluator Tooltip items.

Hide image

Hide image

Clicking on the glyph with the drop down arrow displays a menu that lists all of the visualizers associated with the type of variable. That is correct; multiple External-Viewer Debugger Visualizers can be registered for the same type.

    Where can Debugger Visualizers be used

Visualizers are available throughout the IDE. They work in the following debugger related windows.

  • Watch List
  • Local Variables
  • Debug Inspector
  • Evaluate / Modify
  • Evaluator Tooltips

While you can enable/disable a visualizer in the Watches properties dialog, it is also possible to disable a visualizer in the Options dialog. Navigate to the Debugger Options | Visualizers option page to view a list of installed visualizers. This dialog is shown later on in this article once you have installed the sample visualizer.

You may want to disable a registered Value-Replacer visualizer in favour of a different Value-Replacer visualizer for the same type, since only one Value-Replacer visualizer can be active for a given type.

    Disabling visualizers

If you want to see the default textual representation, there are three ways to disable a visualizer for a specific type.

  1. Edit the Watch and uncheck the ‘Use visualizer’ check box.

Hide image

  1. Disable the visualizer in the Tools | Options | Debugger Options | Visualizers list of available visualizers.
  2. Typecast the Watch name. In the example used above that would be Double(FDateTime). NOTE: This will not always work.

    Creating your own visualizers

Hopefully you now have an idea of what a debugger visualizers and how they can improve your debugging experience. Now we’ll create both types of Debugger Visualizers to work with the TColor type. For those that have never created an IDE Expert before, we’ll start with some required knowledge.

    Writing IDE Experts - Overview

There are two deployment options for IDE Experts, packages or DLL’s. Both have their advantages. Packages are instant on; you can install it within the IDE you are using to do some testing. DLL’s require you to either restart you IDE (and updating the registry) or running another instance of the IDE to debug the expert.

For this article our Debugger Visualizers will be located in a package.

NOTE: Be aware that when using packages the names of the units must be unique within all packages loaded by the IDE.

    Project Group setup

Run Delphi 2010 and create a new package project.

Save the project in a new folder with a unique name. I’ve named my package ColorVisualizer2010.

NOTE: I don’t use the package Suffix or Prefix options (which are called “Shared object name” in Delphi 2010).

The first visualizer we will create is the Value-Replacer.

Add a new unit to the package project and save it as CVAddin.pas.

Add a new frame to the package project and save it as CVViewerFrame.pas.

Right click on the Requires node in the project manager for the package and select the Add Reference... command from the menu.

In the Package Name text edit type in “designide” and click OK. This is the package that gives us access to the IDE Open Tools API functionality. By requiring this package we can now refer to the various interfaces of the Open Tools API.

Add a new project to the project group. This time add a VCL Forms Application, this is the application we’ll debug in order to test our Debugger Visualizer. Save the project as VisualizerTestProj.

Save the unit in the VisualizerTestProj as MainForm.

Save the project group as ColorVisualizer2010Group.

The following screen shot shows how your project manager should look.

Hide image

    Registering the Debugger Visualizer

The Open Tools API uses interfaces heavily. Almost all functionality requires you to register a class that implements specific interfaces. For any visualizer to work it must be registered with the IDE. The way to register your notifier is to call the RegisterDebugVisualizer method on the IOTADebuggerServices interface. This method takes a single interface parameter of type IOTADebuggerVisualizer.

This means that we must create a class that implements this interface. You want this class to descend from TInterfacedObject. Since we are implementing the IOTADebuggerVisualizer interface we also need to implement the methods defined on that interface.

TColorVisualizer = class(TInterfacedObject, IOTADebuggerVisualizer)

public
  procedure GetSupportedType(Index: Integer; var TypeName: string;
    var AllDescendents: Boolean);
  function GetSupportedTypeCount: Integer;
  function GetVisualizerDescription: string;
  function GetVisualizerIdentifier: string;
  function GetVisualizerName: string;
end;

The class needs to return the results for each of these methods.

GetVisualizerName – Used to show the name of the visualizer in the options dialog.

GetVisualizerDescription – Description for the visualizer that appears in the options dialog.

GetVisualizerIdentifier – This should be a unique identifier for the visualizer. I recommend prefixing it with your company name.

GetSupportedTypeCount – Return the number of types your visualizer will handle.

GetSupportedType – This method is called internally the number of times that the GetSupportedType Count method returns. The index is the iteration of the count. Return the name of the type you want to handle in the TypeName parameter. For our example this will be TColor for Delphi.

NOTE: The AllDescendents parameter is ignored in the Delphi 2010 release of the visualizers functionality. So you need to register descendants separately by returning a GetSupportedTypeCount that includes all descendent classes you want to handle.

While we have created a class that implements the IOTADebuggerVisualizer class, registering it will make the visualizer show in the list of available visualizers. But it isn’t accessible since we need to implement at least one more interface first. Currently our class is not a visualizer of any use as it needs to implement either the IOTADebuggerVisualizerValueReplacer or IOTADebuggerVisualizerExternalViewer interface.

    Registering the Visualizer

Before implementing the final interface, let’s register the visualizer. When the IDE loads a package it scans each of the units within the package looking for a Register (the name is case sensitive) procedure. If it finds one, it calls it and this is how most packages should register itself into the IDE.

The following code handles the registration of the visualizer.

var
  _Color: IOTADebuggerVisualizer;

procedure Register;
var
   LServices: IOTADebuggerServices;
begin
  if Supports(BorlandIDEServices, IOTADebuggerServices, LServices) then
  begin
    _Color := TColorVisualizer.Create;
    LServices.RegisterDebugVisualizer(_Color);
  end;
end;

procedure RemoveVisualizer;
var
  LServices: IOTADebuggerServices;
begin
  if Supports(BorlandIDEServices, IOTADebuggerServices, LServices) then
  begin
    LServices.UnregisterDebugVisualizer(_Color);
    _Color := nil;
  end;
end;

initialization
finalization
  RemoveVisualizer;

When writing code that will run within the IDE, it is recommended you be defensive with your method implementations. Remember one simple bug can bring the IDE crashing down, and you don’t want to do that.

Instead of using Supports on the BorlandIDEServices global variable (which is declared in the ToolsAPI unit) we could have used an “as” cast and gotten the same result. Using Supports is safer though since it won’t raise an exception if the BorlandIDEServices variable does not implement the IOTADebuggerServices interface.

    Implementing the Visualizer Interfaces

With two types of visualizers you’d be correct in thinking there are two different interfaces that can be implemented.

Value-Replacer

To create a Value-Replacer visualizer, implement the IOTADebuggerVisualizerValueReplacer interface.

  IOTADebuggerVisualizerValueReplacer = interface(IOTADebuggerVisualizer)
    ['{6BBFB765-E76F-449D-B059-A794FA06F917}']
    function GetReplacementValue(const Expression, TypeName, EvalResult: string): string;
  end;

Add the interface to the class and add the GetReplacementValue method to the class definition. Your class should now look like the following.

TColorVisualizer = class(TInterfacedObject, IOTADebuggerVisualizer,
  IOTADebuggerVisualizerValueReplacer)
public
  function GetReplacementValue(const Expression: string;
    const TypeName: string; const EvalResult: string): string;
  procedure GetSupportedType(Index: Integer; var TypeName: string;
    var AllDescendents: Boolean);
  function GetSupportedTypeCount: Integer;
  function GetVisualizerDescription: string;
  function GetVisualizerIdentifier: string;
  function GetVisualizerName: string;
end;

For our sample visualizer the implementation of the GetReplacementValue is:

function TColorVisualizer.GetReplacementValue(const Expression, TypeName,
  EvalResult: string): string;
begin
  Result := ColorToString(StrToInt(EvalResult));
end;

Expression is the name of the variable.

TypeName is the name of the type that the expression parameter is.

EvalResult is the default textual representation of the evaluated result of the Expression.

The ColorToString method is declared in the Graphics.pas unit. It returns a string representation of some default VCL and Windows colors, such as clNavy and clBtnFace. This is an improvement when compared to the default integer based representation that a color normally is. If the color isn’t predefined then the result is a formatted as hex.

External-Viewer

To create an External-Viewer visualizer, implement the IOTADebuggerVisualizerExternalViewer.

IOTADebuggerVisualizerExternalViewer = interface(IOTADebuggerVisualizer)
  function GetMenuText: string;
  function Show(const Expression, TypeName, EvalResult: string;
    SuggestedLeft, SuggestedTop: Integer): IOTADebuggerVisualizerExternalViewerUpdater;
end;

The first thing that should stand out from the interface definition is that it returns a new interface. Yes, external viewer visualizers are a little more complex than their Value-Replacer compatriots.

The methods that need to be implemented on the IOTADebuggerVisualizerExternalViewer are:

GetMenuText – Remember that External-Viewer visualizers are invoked from a popup menu. This method should return the caption of the menu item.

Show – This is the method that the IDE calls when the user selects your visualizers menu item. The Expression, TypeName and EvalResult parameters are the same as when the GetReplacementValue method is called on the IOTADebuggerVisualizerValueReplacer interface. The SuggestedLeft and SuggestedTop parameters are the recommended screen location for the viewer to use.

Your show method should create the window that displays the expression and then return an IOTADebuggerVisualizerExternalViewerUpdater interface reference. This means you must create a class that implements this IOTADebuggerVisualizerExternalViewerUpdater interface, which for this example will be the frame used for the user interface.

IOTADebuggerVisualizerExternalViewerUpdater = interface
  procedure CloseVisualizer;
  procedure MarkUnavailable(Reason: TOTAVisualizerUnavailableReason);
  procedure RefreshVisualizer(const Expression, TypeName, EvalResult: string);  
  procedure SetClosedCallback(ClosedProc: TOTAVisualizerClosedProcedure);
end;

CloseVisualizer – When this method is called you should close your visualizers window. This means that the thread that was responsible for invoking the visualizer has been destroyed.

MarkUnavailable – This method is called when the visualizer is unavailable. Currently the reason can be either “Out of scope” or “Process not accessible”.

RefreshVisualizer – This method is called whenever the visualizer needs to be updated. This method will be called in response to the user using the Evaluate/Modify dialog to modify the expression your visualizer is displaying. If the visualizer is not visible, it doesn’t display anything.

SetClosedCallback – Call the method which is passed as the ClosedProc parameter when your visualizer window is closed so that the IDE stops sending the RefreshVisualizer message. This means you need to save the passed parameter for later use.

The following is the Show method implementation. This code calls a class method on the frame used to display the visualizer details to the user. I’ve purposely hidden the method implementation from this article since it goes beyond the scope (and space) for the article. All you really need to know is that the frame designed for the visualizer is parented on a dockable IDE form.

function TColorVisualizer.Show(const Expression: string; const TypeName: string;
  const EvalResult: string; SuggestedLeft: Integer;
  SuggestedTop: Integer): IOTADebuggerVisualizerExternalViewerUpdater;
begin
  Result := TfmCVViewer.CreateAndShow(Expression, TypeName, EvalResult, SuggestedLeft, SuggestedTop);
end;

    Calling methods on expressions

When creating an External-Viewer visualizer one thing you may want to do is call a method on the expression the visualizer displaying. For example, you might have a TTable visualizer and want to display the current contents of the active record as well as displaying the total record count for the table.

There is a specific technique for doing this and it has not been covered in this article. It will be discussed in the next edition.

    Supporting C++

I will preface this section with the following comment - I am not a C++ developer.

To support the C++ personality with this visualizer we need to make two changes. The first is the change the supported count to return 2 and the second is to provide the fully qualified type name as the TypeName parameter in the GetSupportedType method. This has been done in the source code that is included with the article.

Thank you to the two C++ developers that tested and provided feedback on the visualizer in the C++ personality.

    Creating the visualizer user interface

The frame that was originally added to the project is what will be displayed to the user. The user interface is very basic with just a panel, popup menu and action list on it. The actual frames color is what is used to show the expressions value as a color. The panel is used to display the name of the color. The action list contains actions for copying the Expression and Evaluated Result to the clipboard and the popup menu contains those actions. Double clicking on the frame copies the Evaluated Result to the clipboard, and double clicking on the panel copies the Expression name to the clipboard.

The screen shot below shows the frame at design time.

Hide image

    Installing the visualizer

To install the visualizer, right click on the package name in the project manager and select the Install command. A dialog will display confirming the status of the install attempt (success or failure).

Once installed, you can also see your visualizer in the Debugger Options | Visualizers section of the Tools | Options dialog.

 

Hide image
Click to see full-sized image

    The finished visualizer in action

The final thing to show is the visualizer in use within the IDE.

Running the provided test project the application has a single form with three buttons on it.

The screen shot below shows the test projects user interface. The TColor property being changed is the forms Color property. This means that the background color of the form will change when clicking on one of the provided buttons.

Hide image

Clicking on the first button and activating the visualizer by selecting the Show Color command from the menu shows the visualizer with a clBtnFace background and the ColorToString representation of the value as clBtnFace.

Hide image

After selecting the Show Color command from the popup menu, the visualiser’s viewer is displayed.

Hide image

Stepping over the color assignment to navy line then changes the visualizer to display a navy background with clNavy text.

Hide image

Also note in the screen shot above where the Value-Replacer part of the visualizer is also shown.

If you select a custom color after selecting the Select a color... button in the test project the hex format of the expression is displayed (as shown below).

Hide image

    Future Enhancements

  • Allow custom painting added to the Value-Replacer visualizer type. This should then allow a Value-Replacer version of the TColor visualizer to just paint the colour next to the formatted TColor value, instead of requiring a popup window to show this information.
  • It would be nice to be able to include a screen capture associated with a visualizer that was displayed in the registered visualizers list in the options dialog.
  • Project specific enabling and disabling of visualizers.

    Conclusion

I have no doubt Debugger Visualizers will improve your debugging efficiency. This will certainly be the case when you need to drill into and call methods on the expression being visualized.

The source code and test project is available to subscribers.

Remember to catch the follow up article on how to call methods on expressions in the next edition. There will also be details of how to use the IDE wizard I’m creating to easily create the base code for your own visualizers.

About the author of this article:

Jeremy North began programming with Delphi in 1997 while at college in the USA, and since his return to Australia he has kept pace with emerging windows technologies. More recently he has concentrated his focus on writing components and IDE experts, as well as creating tools to help other developers work more efficiently. Some of these components and experts are available to the public on his JED Software website: http://www.jed-software.com Some are free, while others incur a small charge. So let’s go visit him at his site!

Server Response from: ETNASC03