Debugger Visualizer for Non-Serializable Types

March 19, 2014

no comments

A debugger visualizer provides a rich way to “visualize” in some sense a .NET object within Visual Studio while debugging. Writing a basic debugger visualizer is simple enough: create a Class Library project with a class that derives from DialogDebuggerVisualizer and override the Show method. Inside the Show method, a call to IVisualizerObjectProvider.GetObject method (the interface is provided in an argument to Show) retrieves the object in question. The next step would be to create the actual “visualizer”and show it with a call to IDialogVisualizerService.ShowDialog method.

To actually advertise the existence of the visualizer, an assembly level attribute named DebuggerVisualizer needs to be added and the resulting assembly DLL placed in one of the two following folders:

{VSInstallDir}\Common7\Packages\Debugger\Visualizers or {MyDocuments}\Visual Studio 20xx\Visualizers, the latter being more convenient and accessible to non-admin accounts.

Debugger visualizer assemblies must be loaded into the target process (debuggee), as well as the Visual Studio process (the debugger). To be viewed inside visual studio, the object must be serialized into some stream and deserialized by Visual Studio, so that it can provide an object to the visualizer. The problem is that for non-Serializable types, the simple approach cannot work.

Let’s take an example. Suppose we want to write a visualizer for the System.Windows.Media.Imaging.BitmapSource type (from WPF). BitmapSource is an abstract base class for various bitmap implementations, such as BitmapImage and WritableBitmap, so any of those would be “visualizable” if we can implement a visualizer for BitmapSource. BitmapSource, unfortunately, is not marked as Serializable, nor are any of its derived types. This means that the simple approach of calling the preceding GetObject would fail.

For these case, the debugger visualizer must serialize and deserialize the object in a way that provides enough information for the the actual “visualization” (whatever that may be for the particular type).

For this purpose, we have to create a class that derives from VisualizerObjectSource and override the GetData method; inside, we’re provided with the target object and a Stream to serialize into. This code executes inside the debuggee process where the actual object is. Then the stream is transferred to the debugger’s process for consumption by the other side of the visualizer (as we’ll soon see).

For a BitmapSource, serialization can be achieved by writing all the pixels data into the stream along with basic metadata, such as the with and height of the bitmap. Here’s one way to do this:

class BitmapSourceVisualizerObjectSource : VisualizerObjectSource {

    public override void GetData(object target, System.IO.Stream outgoingData) {

         var source = target as BitmapSource;

         int stride = (source.PixelWidth * source.Format.BitsPerPixel + 7) / 8;

         var writer = new BinaryWriter(outgoingData);

         writer.Write(source.PixelWidth);

         writer.Write(source.PixelHeight);

         writer.Write(stride);

         writer.Write(typeof(PixelFormat).GetProperty(“Guid“, BindingFlags.NonPublic | BindingFlags.Instance).

                 GetValue(source.Format).ToString());

         var data = new byte;

         source.CopyPixels(data, stride, 0);

         outgoingData.Write(data, 0, data.Length);

    }

}

Writing the pixel information can be achieved with the BitmapSource.CopyPixels method. The actual challenge here was the pixel format (Format property of type PixelFormat). PixelFormat is a structure that is marked [Serializable], so I thought that all I need is to create a BinaryFormatter and call Serialize on it with the PixelFormat object. When I tried it, in the deserialization code, the PixelFormat object was empty. I was puzzled, and finally opened .NET Reflector to look at the full type. This is how it looks:

    [Serializable, StructLayout(LayoutKind.Sequential), TypeConverter(typeof(PixelFormatConverter))]

    public struct PixelFormat : IEquatable<PixelFormat> {

        [NonSerialized]

        private PixelFormatFlags _flags;

        [NonSerialized]

        private PixelFormatEnum _format;

        [NonSerialized]

        private uint _bitsPerPixel;

        [NonSerialized]

        private SecurityCriticalDataForSet<System.Guid> _guidFormat;

        [NonSerialized]

        private static readonly System.Guid WICPixelFormatPhotonFirst;

        [NonSerialized]

        private static readonly System.Guid WICPixelFormatPhotonLast;

   

    }

It seems that all important members are marked as [NonSerialized]… not good. Digging deeper, it seems the pixel format is uniquely identified by a Guid, so I just need to serialize that. Unfortunately, it’s not public, so I had to use reflection to get the value of the Guid and serialize that.

On the receiving end,  the actual visualizer must call the GetData method (of IVisualizerObjectProvider) instead of GetObject, and use the returned Stream to deserialize and rebuild the object:

   public class BitmapSourceVisualizer : DialogDebuggerVisualizer {

      protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider) {

         var stm = objectProvider.GetData();

         var reader = new BinaryReader(stm);

         int width = reader.ReadInt32();

         int height = reader.ReadInt32();

         int stride = reader.ReadInt32();

         Guid guid = Guid.Parse(reader.ReadString());

         PixelFormat format = (PixelFormat)Activator.CreateInstance(typeof(PixelFormat),

            BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.CreateInstance,

            null, new object[] { guid }, null);

         var data = new byte[height * stride];

         stm.Read(data, 0, data.Length);

         var source = BitmapSource.Create(width, height, 0, 0, format, null, data, stride);

Here again, I have to construct a PixelFormat using reflection because of the internal constructor I want to use with the received Guid.

The actual BitmapSource is constructed by the static factory method BitmapSource.Create that uses all the deserialized metadata and the actual pixel data.

The next thing to do is to present the visualizer window. This requires a WinForms Form or Control. Since we need to visualize a BitmapSource, ideally we would want to use a WPF user control to do that (that would probably contain at least an Image element). For that we can use the ElementHost WinForms control that is used to host a WPF element.

I’ve created a simple WPF UserControl called BitmapViewer, so all that’s needed now is to wrap it inside an ElementHost, which in itself inside a Form (this works without a form, too, because a Control-derived type is accepted, but in this case there is no control over other form-level properties, such as its text).

Here’s the rest of the code inside the Show method:

var wpfHost = new ElementHost {

   Child = new BitmapViewer(source),

   Dock = System.Windows.Forms.DockStyle.Fill,

};

var form = new Form {

   Text = “BitmapSource Visualizer“,

   Width = Math.Min(500, width),

   Height = Math.Min(500, height),

   Controls = { wpfHost }

};

windowService.ShowDialog(form);

 

All that’s left is the assembly level attribute, pointing to our implementations:

[assembly: DebuggerVisualizer(typeof(ImageDebuggerVisualizer.BitmapSourceVisualizer),

   typeof(ImageDebuggerVisualizer.BitmapSourceVisualizerObjectSource),

   Target = typeof(BitmapSource), Description = “BitmapSource Visualizer“)]

After copying the resulting DLL (and the PDB, so that VS doesn’t try to find it in Microsoft’s Symbol Server), an app that encounters a BitmapSource-derived object in a debugging session, can view a BitmapSource:

clip_image001[4]

And the visualizer form itself (a simple Image element, with its Source set to the deserialized BitmapSource):

clip_image002[4]

One thing that may be interesting is how to debug the visualizer itself? Placing breakpoints inside a visualizer while debugging won’t work (that would require debugging Visual Studio itself). Fortunately, there is a way. Visual Studio provides a helper class, VisualizerDevelopmentHost that can be used to simulate activation of a visualizer. For testing purposes I’ve exposed the following simple static method to invoke the visualizer:

public static void Test(BitmapSource src) {

   new VisualizerDevelopmentHost(src, typeof(BitmapSourceVisualizer),

      typeof(BitmapSourceVisualizerObjectSource)).ShowVisualizer();

}

This function can be invoked from some WPF test application.

The complete source code can be downloaded here.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*