Developing an DebuggerVisualizer for Images

I have been working with Visual Studio 2005 Beta 2 quite a bit.  One of the more compelling debugging features in VS2005 Beta 2 is the ability to create new extensions to the debugger for supporting custom visualization of objects that are being inspected.  The basic object types in .NET are already supported by the debugger, but sometimes it can be useful to inspect an object type in a visually customized way, depending on the type of object.  Some examples might be:

  • Visualize business objects in a rich, application-specific manner
  • Visualize RegEx objects at debug time in a way that allows you to interact with them
  • Visualize an Image object at debug time that you might have loaded from disk or via a network call
  • Visualize a custom shape object type used in a 3D Game engine
  • Visualize Windows handle objects and examine details associated with them

Visual Studio 2005 allows you to extend your debugger's visualization capabilities by creating a new DebuggerVisualizer class.  This class simply registers itself with an appropriate assembly-level attribute, and when it is invoked by the debugger, it displays a custom form, control, or dialog that can render the passed type.  It must be installed in a specific subfolder at either the user or the machine level.  It's simple enough that you can build one in 5 minutes.

Building a DebuggerVisualizer in 5 minutes

While on the plane coming home from London this weekend, I built a debugger visualizer for Images.  I earlier read an article in DevX from Julia Lerman that was very helpful with regard to debugger visualizers in general.  There have been some minor changes since Beta 1 in this area, and the process seems to be even easier now.

There are only a few simple steps to building a basic debugger visualizer.  First, you create a new solution as a class library.  You then create a class that derives from DebuggerVisualizer, override it's Show() method, and add an assembly-level attribute that VS 2005 will look for when it starts a debugging session.  The next step is to create a custom Form object to display the object that is being inspected during debugging.  Finally, you compile it all and copy the assembly to a special directory.  That's it-- you can probably implement one in less than 5 minutes.  I will walk through the steps in detail now.  Alternately, you can download my project if you wish.

Step 1: Create a new project as a C# class library

Create a new project within Visual Studio 2005 Beta 2 as a C# class library with a name of GDIDebugVisualizers.  Delete the Class1.cs file that was added.

Step 2: Add a DebuggerVisualizer item to the project

Right-click on the new project and select Add -> New Item.  Choose the DebuggerVisualizer item type, and give it a name of ImageDebuggerVisualizer.cs.  The new class should look like the code block below:

    1:  using System;
    2:  using System.Collections.Generic;
    3:  using System.Windows.Forms;
    4:  using Microsoft.VisualStudio.DebuggerVisualizers;
    5:   
    6:  namespace GDIDebuggerVisualizers
    7:  {
    8:      // TODO: Add the following to SomeType's defintion to see this visualizer 
    9:      //       when debugging instances of SomeType:
   10:      // 
   11:      //  [DebuggerVisualizer(typeof(ImageDebuggerVisualizer))]
   12:      //  [Serializable]
   13:      //  public class SomeType
   14:      //  {
   15:      //   ...
   16:      //  }
   17:      // 
   18:      /// <summary>
   19:      /// A Visualizer for SomeType.  
   20:      /// </summary>
   21:      public class ImageDebuggerVisualizer : DialogDebuggerVisualizer
   22:      {
   23:          protected override void Show(IDialogVisualizerService windowService, 
   24:                                       IVisualizerObjectProvider objectProvider)
   25:          {
   26:              // TODO: Get the object to display a visualizer for.
   27:              //       Cast the result of objectProvider.GetObject() 
   28:              //       to the type of the object being visualized.
   29:              object data = (object)objectProvider.GetObject();
   30:   
   31:              // TODO: Display your view of the object.
   32:              //       Replace displayForm with your own custom Form or Control.
   33:              using (Form displayForm = new Form())
   34:              {
   35:                  displayForm.Text = data.ToString();
   36:                  windowService.ShowDialog(displayForm);
   37:              }
   38:          }
   39:   
   40:          // TODO: Add the following to your testing code to test the visualizer:
   41:          // 
   42:          //    ImageDebuggerVisualizer.TestShowVisualizer(new SomeType());
   43:          // 
   44:          /// <summary>
   45:          /// Tests the visualizer by hosting it outside of the debugger.
   46:          /// </summary>
   47:          /// <param name="objectToVisualize">The object to display in the visualizer.</param>
   48:         public static void TestShowVisualizer(object objectToVisualize)
   49:          {
   50:              VisualizerDevelopmentHost visualizerHost = 
   51:                  new VisualizerDevelopmentHost(objectToVisualize, typeof(ImageDebuggerVisualizer));
   52:              visualizerHost.ShowVisualizer();
   53:          }
   54:      }
   55:  }

Step 3: Create an custom form for visualization

This is the most interesting part because it is where you render the debug-time object visually.  Simply add a new Form object to the project and call it ImageFormViewer.cs.  In the designer, change the FormBorderStyle to FormBorderStyle.SizableToolWindow.  Next add a PictureBox control to the new form, configure it's Dock property to DockStyle.Fill, and set it's SizeMode property to PictureBoxSizeMode.Zoom.  See the diagram below:

 

Finally, add a new property to this ImageFormViewer class called CurrentImage, implementing get and set in a way that directly returns the picturebox's Image object.  See the code snippet below:

    1:  public Image CurrentImage
    2:  {
    3:     get { return currentPicture.Image; }
    4:     set { currentPicture.Image = value; }
    5:  }

 

Step 4: Modify the code to use the new ImageFormViewer form

Remove the TODO comments after following the instructions, and add the logic to load the custom form.  You will somehow need to send the object itself to this form.  The end result should be something that looks like this:

    1:  using System;
    2:  using System.Collections.Generic;
    3:  using System.Windows.Forms;
    4:  using System.Drawing;
    5:  using Microsoft.VisualStudio.DebuggerVisualizers;
    6:   
    7:  [assembly: System.Diagnostics.DebuggerVisualizer(
    8:   typeof(GDIDebuggerVisualizers.ImageDebuggerVisualizer), typeof(VisualizerObjectSource), 
    9:   Target = typeof(System.Drawing.Image), Description = "Image Visualizer")]
   10:   
   11:  namespace GDIDebuggerVisualizers
   12:  {
   13:      public class ImageDebuggerVisualizer : DialogDebuggerVisualizer
   14:      {
   15:          protected override void Show(IDialogVisualizerService windowService, 
   16:                                       IVisualizerObjectProvider objectProvider)
   17:          {
   18:              Image debugImage = objectProvider.GetObject() as Image;
   19:              if (debugImage != null)
   20:              {
   21:                  using (ImageViewerForm displayForm = new ImageViewerForm())
   22:                  {
   23:                      displayForm.CurrentImage = debugImage;
   24:                      DialogResult result = windowService.ShowDialog(displayForm);
   25:                      if (result == DialogResult.OK)
   26:                      {
   27:                          objectProvider.ReplaceObject(displayForm.CurrentImage);
   28:                      }
   29:                  }
   30:              }
   31:          }
   32:   
   33:         public static void TestShowVisualizer(object objectToVisualize)
   34:          {
   35:              VisualizerDevelopmentHost visualizerHost = 
   36:                  new VisualizerDevelopmentHost(objectToVisualize, typeof(ImageDebuggerVisualizer));
   37:              visualizerHost.ShowVisualizer();
   38:          }
   39:      }
   40:  }

 

Step 5: Compile and Deploy

You can install the new visualizer assembly at a user level or at a machine level.  For the user-level approach, copy the compiled assembly to:  My Documents\Visual Studio 2005\Visualizers.  For a machine-wide approach, copy the assembly to:  \Program Files\Microsoft Visual Studio 8\Common7\Packages\Debugger\Visualizers.  Once this has been copied, you are ready to debug.  In Beta 2, no restart is required because VS 2005 will check for the available visualizers every time you start a debug session.  Of course, you cannot change the list of registered Debugger Visualizers during a debug session.

Test with the Debugger

Testing simply requires that you start a debugging session and inspect an Image object.  The easiest way is to build a simple WinForm application with a PictureBox control configured at design-time to load an external bitmap file.  The result will look like the picture below:

 

Insert a breakpoint in the code, and start debugging.  When you hit the breakpoint, inspect the picturebox Image property by hovering over it.  There should be a magnifying glass icon that appears, with a drop-down associated for the new Debugger Visualization.  The screenshots below show this in action:

 

When you clock on the Image Visualizer in the drop-down menu, your new Debugger Visualizer is launched.  The screenshot below shows this using my implementation (I support a context menu for loading and saving images as well). 

 

Testing without Debugging

There is a way of testing out the visualizer without actually debugging by using an instance of VisualizerDevelopmentHost.  This class invokes the visualizer as if it were in a debugging session, and is for testing purposes.  The template-generated code from Lines 50-52 of the code for Step 2 implements this approach via a static method.  You can create a simple project in the same solution, add a project reference to the visualizer, and call the static method to test this out.  Don't forget to add a reference to the Microsoft.VisualStudio.DebuggerVisualizers assembly in this test project-- you need this reference because the visualizer depends on it. 

Other Notes

There are some important things to know about DebuggerVisualizer implementations.  I received some updates from Scott Nonnenberg as well

  • You can have one or more System.Diagnostics.DebuggerVisualizer attributes per assembly.
  • You need an instance of the System.Diagnostics.DebuggerVisualizer for every type your visualizer supports (can be multiple here).
  • You can have multiple debugger visualizers implemented within an assembly. 
  • This form implementation can be smart enough to handle different types which are semantically similar but taxonomically distinct.
  • You can support changes to the object by calling the ReplaceObject() method of the IVisualizerObjectProvider (examine the DialogResult first).

Next Steps

In any case, this was an interim step, and I am thinking about building more interesting debugger visualizers now.  The most compelling implementations to me would probably be some sort of Web Services debugger visualizer.  I will look around for other more sophisticated debugger visualizers when I get a chance.

This posting is provided "AS IS" with no warranties, and confers no rights.