Advanced Visual Studio debugging: automatic expansion of watched variables

I've been using Visual C++ (and afterwards Visual Studio) since it was 16-bit, back in version 1.52. OK, maybe that's not so long ago, relatively (11 short years), but the point is that in spite of having followed the Visual Studio technology over this period, I've so far been completely unaware of the autoexp.dat file.

This feature of the Visual Studio was brought to my attention by Ahsan Ali, a programmer in the Inventor Engineering team who was based over in Bangalore at the same time I was (we had both previously worked in the US - he had come across from Tualatin while I had moved there from San Rafael). During a recent technology discussion, Ahsan shared some information with our Bangalore-based team on some advanced debugging techniques, and I thought it would be a great topic for this blog.

The autoexp.dat file's default location (for Visual Studio 2005) is:

C:\Program Files\Microsoft Visual Studio 8\Common7\Packages\Debugger\autoexp.dat

It's basically a text file that informs the Visual Studio IDE which information is especially relevant in a particular class, so that this information can be displayed automatically (and in the appropriate format) in the watch window, without you having to expand the various datatypes. The clicks you use to get down to data all add up, especially when dealing with a particularly repetitive debugging task, so this feature can really be helpful. MSDN refers to it here.

The technique seems to be specific for native (presumably C++?) code, but the above link refers to techniques to implement this type of functionality for VB.NET and C#.

So let's look at an example of the problem - as it relates to ObjectARX - and how this technique helps. Here's the default view of two very common ObjectARX datatypes, AcGePoint3d and AcGeMatrix3d:

Watch_window_1

The default view of the pickpnt variable (an AcGePoint3d) is OK, but all those zeroes do make your eyes hurt. The data displayed for xform (an AcGeMatrix3D) tells you nothing whatsoever of any use - it's the address of the 2D array holding the matrix contents. Here's what you get when you expand these two variables:

Watch_window_1b_1

You need 5 clicks just to determine that the xform variable is actually the unit matrix, which is way too much.

So what can we do? The autoexp.dat rules format is very simple and is documented in the header of the file itself. I won't reproduce the whole description of the format here, aside from this brief statement:

An AutoExpand rule is a line with the name of a type, an equals sign, and text with replaceable parts in angle brackets. The part in angle brackets names a member of the type and an  optional Watch format specifier.

Thereafter follow some specifics regarding the syntax - for an alternative source of information regarding this, you might also try this CodeGuru article. I'd also recommend looking at the various types listed in the file itself - as usual real-life examples often paint better pictures than abstract descriptions.

So, let's look at what we can do for AcGePoint3d and AcGeMatrix3d. Here are the two entries I added to the autoexp.dat file:

AcGePoint3d =x=<x,g> y=<y,g> z=<z,g>

AcGeMatrix3d =<entry[0][0],g> <entry[0][1],g> <entry[0][2],g> <entry[0][3],g>,<entry[1][0],g> <entry[1][1],g> <entry[1][2],g> <entry[1][3],g>,<entry[2][0],g> <entry[2][1],g> <entry[2][2],g> <entry[2][3],g>,<entry[3][0],g> <entry[3][1],g> <entry[3][2],g> <entry[3][3],g>

The first is fairly simple: it simply tells Visual Studio to display each of the X, Y and Z values of the point, but using the "g" type specifier. Without going into details, this means that they are floating-point values that should be abbreviated to significant digits only. I chose to leave in the labels, x, y & z, to improve readability.

The second is clearly longer - once again it uses the "g" type specifier for each of the 16 entries in the 4x4 matrix. Given the volume of information in the matrix class, I decided to leave out the labels, simply listing the contents with each row separated each by a comma.

Here are the results:

Watch_window_2a

When we have something more meaningful contained in the variables, the descriptions will get longer, of course:

Watch_window_2b

Watch_window_2c

A few tips about the entries in autoexp.dat:

  • Don't just add them to the end of the file: they should be part of the [AutoExpand] section, not the [Visualizer] section. I placed mine at line 147.
  • It seems that the string itself is limited to 256 characters to the right of the first equals sign (in this day and age - can you imagine?). I was lucky - the AcGeMatrix3d rule eventually came to exactly 256 characters (which is how I found out about the limitation - I had to squeeze out a few redundant spaces for it to work).
  • I haven't been able to get multiple lines to display - which would be especially useful for the matrix class, of course. If someone can work it out or find the information on the internet, please post a comment!
  • It would also be great to do conditional display of data, especially for union types such as the good old resbuf. While this is not supported directly, you can develop an AddIn to make it work, apparently. See this article for more details.

9 responses to “Advanced Visual Studio debugging: automatic expansion of watched variables”

  1. Fernando Malard Avatar
    Fernando Malard

    Hi Kean,

    Great article. This is very useful indeed.
    Maybe the ObjectARX team would start to build a standard ObjectARX autoexp.dat file to be shipped within SDK.

    Do you know if VS accepts more than one dat file for this? Maybe it can accept additional files like autoexp1.dat, etc.

    This way the ObjectARX classes and types will be displayed much like human information. ๐Ÿ™‚

    Regards,
    Fernando.

  2. Hi Fernando,

    Thanks for the suggestion. Analogously, the DevTech team used to maintain an ObjectARX-specific usertype.dat file to allow contextual colouring of keywords (back before functioning Intellisense, when Visual Studio did a poor job of parsing types in headers). I see this is another strong candidate for posting on the ADN site - that way my team can help it to evolve more regularly (for instance one person in the team is already looking at defining rules for ObjectARX smart-pointers - watch this space).

    As for multiple autoexp.dat files... I haven't rsearched this specifically, but all the indications I've seen so far point to the mechanism working with a single file.

    Regards,

    Kean

  3. On personal opinion, I find this very helpful.
    Guys, I have also posted some more relevant info further on this, not sure if you find it

    useful: bidmaxhost.com/f...

  4. Hi Kean,

    I've just found out a nice way to watch a AcGeMatrix3d in the VS debugger.

    In your autoexp.dat enter a expression for the first row of the matrix. i.e.:

    AcGeMatrix3d =< entry[0][0],f> | < entry[1][0],f> | < entry[2][0],f> | < entry[3][0],f>

    In your Debugger, you have to type an expression for each row of the matrix, that you want to view. Let's say you want to watch AcGeMatrix3d acMat:

    Expression: - - - - - - - - - - - - - - - - - - - Debugger display:
    *(AcGeMatrix3d*)((double*)&acMat+0){0,893613 | 0,000000 | -0,448838, 0,000000}
    *(AcGeMatrix3d*)((double*)&acMat+1){-0,448838 | 0,000000 | -0,893613 | 0,000000}
    *(AcGeMatrix3d*)((double*)&acMat+2){0,000000 | 1,000000 | 0,000000 | 0,000000}
    *(AcGeMatrix3d*)((double*)&acMat+3){-4,277458 | 0,000000 | -18,919671 | 1,000000}

    The trick is, that the double entries of each row are shifted by
    sizeof(double). It's quite a bit of typing - but the rows just differ by one character.

    -- Thomas

  5. Hi again,

    oops - I've mixed up rows and columgs in my previous posting. Sorry !!
    To display a ROW your autoexp.dat should read:

    < entry[0][0],f> | < entry[0][1],f> | < entry[0][2],f> | < entry[0][3],f>

    And in the debugger window you have to add 4,8 and 12 instead of 1,2,3 because the offsets between rows are 4 double's:

    acMat
    *(AcGeMatrix3d*)((double*)&acMat+4)
    *(AcGeMatrix3d*)((double*)&acMat+8)
    *(AcGeMatrix3d*)((double*)&acMat+12)

    Btw.: I recomment to use just the variable name for the first row. So you can see the name even in small debugger windows.

    -- Thomas

  6. Thanks for sharing this tip, Thomas!

    Kean

  7. Hi - it's me again.

    I finally found a tricky solution to display a matrix with VS2005.
    VS2002 and earlier won't work because I use the [Visualizer] feature.

    If you have an AcGeMatrix mat you can view it in the debugger like this:

    -(Mat44*)&mat 0x0012feb0 {line=0x0012feb0 }
    + [0] {0,00 | 0,10 | 0,20 | 0,30}
    + [1] {1,00 | 1,10 | 1,20 | 1,30}
    + [2] {2,00 | 2,10 | 2,20 | 2,30}
    + [3] {3,00 | 3,10 | 3,20 | 3,30}

    Unlike my previous solution you just need one single entry, that displays
    the matrix with all lines expandable.

    How does it work?
    You need two dummy classes with entries in autoexp.dat.

    ======================
    in any *.cpp file in your project:
    ======================
    class DebugHelper_Line4 { double entry[4]; };
    class Mat44 { DebugHelper_Line4 line[4]; };
    static const Mat44 *_needThisToGetMat44DebugDisplayToWork=0;

    ============
    in autoexp.dat:
    ============
    [AutoExpand]
    DebugHelper_Line4 =< entry[0],f> | < entry[1],f> | < entry[2],f> | < entry[3],f>

    [Visualizer]
    Mat44{
    children
    (
    #array
    (
    expr : ($e.line)[$i],
    size : 4
    )
    )
    };

    I tried a lot to write a Visualizer for AcGeMatrix directly.
    The problem is, that you can't have multiline-expressions in the
    preview() sections and you can only have one element displayed
    in the children() section. So this element must have it's own
    visualizer. This is why I needed DebugHelper_Line4.

    Enjoy
    -- Thomas

  8. It's even possible to write a [Visualizer] for AcGeMatrix3d directly:

    [In any cpp file of your project]
    class DebugHelper_Line4 { double entry[4]; };
    static const DebugHelper_Line4 *_needThisToGetMat44DebugDisplayToWork=0;

    [AutoExpand]
    DebugHelper_Line4 =< entry[0],f> | < entry[1],f> | < entry[2],f> | < entry[3],f>

    [Visualizer]
    AcGeMatrix3d{
    children
    (
    #array
    (
    expr : *((DebugHelper_Line4*)($e.entry[$i])),
    size : 4
    )
    )
    }

    Now an AcGeMatrix3d will look like this in the debugger:

    [-]mat {entry=0x0012feb8 } AcGeMatrix3d
    [+][0] {0,0 | 0,1 | 0,2 | 0,3} DebugHelper_Line4
    [+][1] {1,0 | 1,1 | 1,2 | 1,3} DebugHelper_Line4
    [+][2] {2,0 | 2,1 | 2,2 | 2,3} DebugHelper_Line4
    [+][3] {3,0 | 3,1 | 3,2 | 3,3} DebugHelper_Line4

    So you don't need any nasty cast-typing in the debugger window and the dummy class Mat44 is obsolete.
    Sorry for my premature postings. I think this is really the final one ๐Ÿ˜‰

    -- Thomas

  9. Don't be sorry - thanks for participating!

    Kean

Leave a Reply to ocnsss Cancel reply

Your email address will not be published. Required fields are marked *