Click here to Skip to main content
15,885,278 members
Articles / Programming Languages / C#
Article

Building a Visual Studio DebuggerVisualizer with a Custom Serializer

6 May 2008CPOL5 min read 30.2K   87   12  
For most any Serializeable object, making a DebuggerVisualizer is exceeding simple and examples abound. However, if you are trying to build a DebuggerVisualizer for an object which is not Serializable or takes too long to Serialize and Deserialize, things are not quite so simple.

This article is in the Product Showcase section for our sponsors at CodeProject. These articles are intended to provide you with information on products and services that we consider useful and of value to developers.

Image 1 image001.jpg

Introduction

DebuggerVisualizers allow you to customize the way objects are displayed while debugging in Visual Studio. This is useful for a great number of applications. Some examples might be:

  • Building simplified models of complex objects.
  • Rendering objects which are composed of more than just basic types into a human viewable form.
  • Presenting debug data in a more organized, and easily accessible, way.

In our DotImage Toolkit, we provide an AtalaImage object in which the image data itself is stored. Using a DebuggerVisualizer allows us to inspect that image visually, on the fly, while debugging. As you might expect, this is much easier than injecting statements in the code to write out intermediary images or trying to interpret the raw contents of the image memory.

The process for building Visual Studio Debugger Visualizers is extremely straightforward for most objects. However, for objects which do not implement ISerializable, serialize too slowly or do not contain all of the information you may wish to see within their serialized form, it is necessary to package a custom serializer along with your Debugger Visualizer.

Factors to Consider

  1. Visualizers with custom serializers need to be able to reference the assemblies for the object which is being custom serialized.
  2. Visualizers with custom serializers depend on the visualizer, serializer and the current project to all be referencing the same version assemblies.
  3. Visualizers will time out if the serialization or deserialization takes too long.
  4. Visualizers only work with Visual Studio 2005 or newer.

Code

When building a Visualizer with custom serialization, there are four main components in play: the Custom Serializer, the Transport Object, the Visualizer/Deserializer and the Data Viewer.

The Custom Serializer (AtalaImageSerializer.cs)

A custom serializer lets you define serialization for an object in any way you like, even if it is already serializable. It is instantiated and run on the debugee side and so has access to the entire debug environment. Implementing a Custom Serializer is as simple as overriding the GetData method in VisualizerObjectSource.

By default, we serialize AtalaImages to PNG format. PNG is great for saving space, but encoding and decoding is much too slow and was causing our DebuggerVisualizer to timeout with large images. To remedy this, we built a custom serializer which outputs to BMP instead. Using BMP has the added benefit of being compatible with System.Windows.Forms.PictureBox, allowing us to not have to load our assemblies on the Debugger side.

C#
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using Atalasoft.Imaging;

namespace AtalaImageDebuggerVisualizer
{
    class AtalaImageSerializer : 
        Microsoft.VisualStudio.DebuggerVisualizers.VisualizerObjectSource
    {
         public override void GetData(object inObject, Stream outStream)
         {
             if (inObject != null && inObject is AtalaImage)
             {
                 AtalaImage atalaImage = inObject as AtalaImage;

                 Bitmap bmp = atalaImage.ToBitmap();
                 AtalaImageTransporter transporter = 
                     new AtalaImageTransporter(bmp, atalaImage.PixelFormat.ToString());

                 BinaryFormatter bf = new BinaryFormatter();
                 bf.Serialize(outStream, transporter);
             }
         }
    }
}

inObject is the object you are are trying to visualize and outStream is your data connection to the Visualizer. outStream is just a normal stream, and so you can dump anything you want into it. In this case, I used a transport object in order to wrap only the information I need for my Visualizer for convienance and code readability.

Visual Studio will timeout if you take too long with your serialization (or deserialization), so the speed of serialization is key. If you are having timeout problems, you may want to check out the following article on The Code Project about speeding up Serialization: Optimizing Serialization in .NET by SimmoTech.

The Transport Object (AtalaImageTransporter.cs)

The transport object’s job is to wrap up all of the information I want to access in my Visualizer into a nice, neat package. Having all of the information in an object also makes Serialization and Deserialization much easier.

C#
using System;
using System.Drawing;
using System.Runtime.Serialization;

namespace AtalaImageDebuggerVisualizer
{
    [Serializable]
    public class AtalaImageTransporter
    {
        public AtalaImageTransporter(Image pic, string pf)
        {
            Picture = pic;
            PixelFormat = pf;
        }

        Image _picture;
        public Image Picture
        {
            get { return _picture; }
            set { _picture = value; }
        }

        string _pixelformat;
        public string PixelFormat
        {
            get { return _pixelformat; }
            set { _pixelformat = value; }
        }

    }
}

For my purposes, the default serialization methods for string and image worked great. If you want more fin-grained control over the serialization of your data, you should make your transport object implement ISerializable.

The Visualizer/Deserializer (AtalaImageVisualizer.cs)

The Visualizer receives your serialized data and is responsible for deserializing it and presenting it to the user. It is instantiated and run on the debugger side, and so only has access to the data provided in objectProvider. Like the custom serializer, all that is needed to implement a DebuggerVisualizer is to inherit from a super class and override one method. In this case, the class is DialogDebuggerVisualizer and the method is Show.

C#
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using Atalasoft.Imaging;
using Microsoft.VisualStudio.DebuggerVisualizers;

namespace AtalaImageDebuggerVisualizer
{
    public class AtalaImageVisualizer : Microsoft.VisualStudio.
                         DebuggerVisualizers.DialogDebuggerVisualizer
    {

        protected override void Show(Microsoft.VisualStudio.
               DebuggerVisualizers.IDialogVisualizerService windowService,
                Microsoft.VisualStudio.DebuggerVisualizers.
                 IVisualizerObjectProvider objectProvider)
        {
            Stream stm = objectProvider.GetData();
            if (stm.Length != 0)
            {
                BinaryFormatter bf = new BinaryFormatter();
                AtalaImageTransporter imageData = 
                    bf.Deserialize(stm) as AtalaImageTransporter;
                if (imageData != null)
                {
                    AtalaImageViewer view = new AtalaImageViewer(imageData);
                    view.ShowDialog();
            }
            }   
        }
    }
}

IVisualizerObjectProvider provides a method named GetObject which returns an object directly. However, depending on how you serialized your data, direct deserialization to an object may not work and so deserializing from a stream is safer.

The Data Viewer (AtalaImageViewer.cs)

The Data Viewer is just a simple form used to display the Visualized data to the user. Using a form is not strictly necessary, but it is standard practice.

C#
using System;
using System.Drawing;
using System.Windows.Forms;

namespace AtalaImageDebuggerVisualizer
{
    public partial class AtalaImageViewer : Form
    {
        public AtalaImageViewer()
        {
            InitializeComponent();
        }

        public AtalaImageViewer( AtalaImageTransporter imageData )
        {
            InitializeComponent();

            PicViewer.Image = imageData.Picture;
            PixelFormatLabel.Text = imageData.PixelFormat;
            PicViewer.SizeMode = PictureBoxSizeMode.Zoom;

            this.Width = imageData.Picture.Width / 2;
            this.Height = imageData.Picture.Height / 2;
        }
    }
}

The Assembly Description (AssemblyInfo.cs)

The DebuggerVisualizerAttribute describes how the visualizer is used to Visual Studio:

C#
[assembly: System.Diagnostics.DebuggerVisualizer(
    typeof(AtalaImageDebuggerVisualizer.AtalaImageVisualizer),
    typeof(AtalaImageDebuggerVisualizer.AtalaImageSerializer),
    Target = typeof(Atalasoft.Imaging.AtalaImage),
    Description = "Atalasoft Image Visualizer" )]

The Format is:

C#
[assembly: System.Diagnostics.DebuggerVisualizer(
    typeof( Your Visualizer ),
    typeof( Your Custom Serializer ),
    Target = typeof( Your Visualized Object ),
    Description = "Description Here" )]

Replacing the Visualized Object

If you want to change your object inside your visualizer and inject it back into the program that is being debugged, you need to do two additional things:

  1. The IVisualizerObjectProvider inside the visualizer provides the ReplaceStream method that is used for sending data back. You need to serialize and send your Transport Object back using this method.
  2. Inside your custom serializer you need to override VisualizerObjectSource’s CreateReplacementObject method. You need to deserialize your transport object, build a replacement for the visualized object and assign that to the target parameter.

Conclusion

This article by no means covers all of the intimate details of DebuggerVisualizers or custom serialization. If you are building one, I encourage you to visit the MSDN documentation which will provide for a much deeper level of understanding.

About Atalasoft

Atalasoft, Inc., is a leading provider of .NET development toolkits for ISVs, integrators, and enterprises with the need to add Enterprise Content Management Imaging (ECMi) to their solutions. Atalasoft’s products are used in many industries including Financial Services, Legal, Healthcare, and Manufacturing with requirements for distributed capture, zero-footprint web viewing, and document markup.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Atalasoft, Inc.
United States United States
At Atalasoft I work with OCR, Raw Image Formats, Exif Data and Pdf Documents. My interests include Machine Learning, Concurrency and Computer Languages.

Comments and Discussions

 
-- There are no messages in this forum --