[Update: a more recent how-to has been posted]

Alright folks. This is the post you've been waiting for. If you have the PDC build of Whidbey at your disposal and you find the debugging experience to be a bit lacking, the following is just for you.

Visualizers are custom viewers meant to be used at debug-time. They are entirely managed, and associated with particular types. Where before DataSet was a horrible mess in the watch window, now the standard DataGrid can be used to view the previously inaccessible data contained therein.

Before we start I must mention that this feature has changed significantly since the PDC build. The general idea hasn't changed, but the implementation has improved tremendously. Unfortunately this does mean that any visualizer you write now will require some effort to port. Beyond this, the feature isn't extremely robust in the PDC build, nor is it supported in languages other than C#. If you do anything wrong, you won't get any nice error messages telling you how to fix it - this is again because the feature is in its infancy in the PDC build.

Now let's rock.

First, we have to use regasm on a specfic DLL: <Whidbey Install Dir>\Common7\IDE\meehost.dll. I recommend pulling up the Visual Studio Command Prompt, which can be found at Start Menu\Programs\Microsoft Visual Studio Whidbey\Visual Studio Tools by default. Simply type 'regasm meehost.dll' and watch the (utter lack of) magic happen.

Visualizers consist of at least two classes. One is on the debugger side to handle UI, and the other is in the debuggee process to get its hands directly on the data. Communication between them is a stream of bytes; they both do serialization and deserialization of the data. In this incarnation of visualizers, you are responsible for making sure that the debuggee-side class is loaded in the debuggee.

Let's create the debugger-side class now. Create a class-library project, and add a reference to the meehost.dll that we just regasm'd. You'll also want to add references to System.Drawing.dll and System.Windows.Forms.dll to support the use of Color and MessageBox below. Now replace all the text in Class1.cs with this:

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Windows.Forms;
using System.Drawing;
namespace ClassLibrary1
{
 public class Class1 : IUIProxyDisplay
 {
  public Class1() {}
  
  public void SetCallback(IUIProxyCallback callback)
  {
   // Create a memory stream then deserialize it 
   byte[] data = callback.GetInitialData();
   MemoryStream memStream = new MemoryStream(data);
   BinaryFormatter dataSerializer = new BinaryFormatter();
   Color color = (Color)dataSerializer.Deserialize(memStream);
   MessageBox.Show(color.ToString());
  }
 }
}

As you can see this visualizer will be very basic. It will only work on Color objects, and it will simply throw up a MessageBox with the ToString() of the Color it gets. UIProxyDisplay is defined in meehost.dll, which is why we had to reference it. Now that we've created this part of the visualizer, we need to put it in its rightful place - right next to meehost.dll. Copy ClassLibrary1.dll to <Whidbey Install Dir>\Common7\IDE\.

Now let's create a quick little test app. Add a new WindowsApplication project to the current solution. To this project we'll be adding the debuggee-side class, and here it is in its entirety:

using System;
using System.Runtime.Serialization.Formatters.Binary;
using System.Windows.Forms;
using System.IO;
namespace WindowsApplication1
{
 public class SerializeProxy
 {
  public static byte[] GetInitialData(object o)
  {
   try
   {
    BinaryFormatter serializer = new BinaryFormatter();
    MemoryStream myStream = new MemoryStream();
    serializer.Serialize(myStream, o);
    return myStream.ToArray();
   }
   catch (Exception e)
   {
    MessageBox.Show(e.ToString());
    MessageBox.Show(e.StackTrace);
    return new byte[0];
   }
  }
  
  public static object CreateReplacementObject(byte[] serializedObject)
  {
   BinaryFormatter objectSerializer = new BinaryFormatter();
   MemoryStream objectStream = new MemoryStream(serializedObject);
   return objectSerializer.Deserialize(objectStream);
  }
  
  public static byte[] InplaceUpdateObject(byte[] data)
  {
   throw new ApplicationException("InPlaceUpdateObject not implemented");
  }
 }
}

GetInitialData is all we care about at the moment. The debugger will supply the object in question, and SerializeProxy will turn it into an array of bytes which the debugger will then supply to debugger-side class.

Now we have all the infrastructure in place. Next we need to let the debugger know about these classes we've written. This is probably the second-ugliest part of this whole process - go to \Common7\Packages\Debugger and open csautoexp.dat in your text editor of choice. What's in there by default is useless garbage and can be removed and replaced with these three lines:

[DebuggerVisualProxy(@"<Whidbey Install Dir>\Common7\IDE\ClassLibrary1.dll", "ClassLibrary1.Class1")]
[DebuggerProxy("", "WindowsApplication1.SerializeProxy")]
System.Drawing.Color

With this C#-like syntax we're associating ClassLibrary1.Class1 (debugger-side class) and WindowsApplication1.SerializeProxy (debuggee-side class) with the framework class System.Drawing.Color.

Now, to test it out. I recommend that you add some use of the System.Drawing.Color class to your test application. :0)

Start debugging and break in a place where a variable of type Color is in scope. In the watch window, in the value column, for any Color variable, there should be a little gray box with an ellipse ("...") on it. Click on this and the MessageBox should pop up with the ToString() of the Color. Voila!

I'm sure you can imagine all the great ways to use this technology. DataSets and Images are the two obvious applications. I look forward to seeing all the different kinds of visualizers .NET-heads will create in the future. Have fun, and again, I promise that things are much better in more recent builds. :0)