Microsoft & .NETVisual C#Improving Visual C++ Debugging with Better Data Display

Improving Visual C++ Debugging with Better Data Display

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

The ability to customize the summary information for a type in the Watch and Variable windows in the Visual C++ debugger has existed for a long time, and for this reason, the feature doesn’t receive a lot of coverage in new articles and books on the Visual C++ debugger. Even though the display information customization feature is old, it is still incredibly useful for boosting developer productivity, and it is worth reminding more experienced developers that the feature still exists and demonstrating to more recent converts to Visual C++ how it can be used.

The key to customizing type display in the Visual C++ debugger is the autoexp.dat file. This file, which is located in the <install_path>Common7PackagesDebugger folder in a Visual Studio 2008 installation, uses a custom syntax to instruct the debugger how to display the summarized information for a particular type. At its simplest form, the syntax is type=[text]<member[,format]>… For a simple type, such as the MFC CPoint class, the autoexp.dat display specification is CPoint =x=<x> y=<y>, which translates to the display shown in Figure 1.

Figure 1: Simple Type Information Display: CPoint

Without the autoexp.dat entry, CPoint will display as shown in Figure 2, which is obviously a lot less useful from a debugging perspective.

Figure 2: CPoint Debug Display without autoexp.dat

The debugger will use the display information for a base type when a derived type has no custom display information of its own. For the case where a type has multiple base types that have custom display information, the first type in the inheritance list that has custom debug information will be used. In this case, defining the debug display for the derived type by combining the autoexp.dat entries for the base types will be the best course of action.

C++ Debug Visualizers

Prior to the release of Visual Studio 2005, autoexp.dat was fairly static from release to release. Visual Studio 2005 saw the release of debug visualizers in the managed world; they allowed for the display of type information in a custom dialog box, and although the increase in debug display in Visual C++ was not as dramatic, the debug display engine in Visual C++ did receive a significant upgrade, and there was a huge increase in autoexp.dat type coverage. The RTM version of Visual C++ 2003 had a 6KB autoexp.dat, whereas the same file for RTM Visual C++ 2005 was 53KB.

One of the areas that received the most focused coverage increase was the STL. Prior to Visual C++ 2005, inspecting the contents of a STL container often required the use of raw pointers and the memory window to trace through the in-memory layout of the collection manually. Less tedious but no less frustrating was the experience of stopping the debugger, adding code to spit out the contents of the collection to the console, and re-running the application.

Figure 3 shows the before- and after-Visual Studio 2005 display of a STL vector containing three elements in the debugger.

Figure 3: STL Vector Display in the Debugger

Looking at the display in Figure 3, it is apparent that there are two display customizations taking place: the summary information for vector is being customized to display the vector length followed by the vector contents; the member variable display is being customized to display each vector element rather than the raw pointers to _Myfirst and _Mylast. The autoexp.dat display for vector is shown below, and the two separate specifications for preview display and children display can be seen.

std::vector<*>{
   children
   (
      #array
      (
         expr :  ($e._Myfirst)[$i],
         size :  $e._Mylast-$e._Myfirst
      )
   )
   preview
   (
      #(
         "[", $e._Mylast - $e._Myfirst , "](",
         #array
         (
            expr : ($e._Myfirst)[$i],
            size : $e._Mylast-$e._Myfirst
         ),
         ")"
      )
   )
}

The #array keyword tells the debugger that it should loop over the object for a certain number of iterations specified using the size variable, with the $i pseudo-variable specifying the element number. The other supported collection iteration constructs are #tree (which allows head, skip, size, left, and right variables to be defined) and #list (head, size, and next are specified).

In addition to the preview and children display settings that can be specified, it is possible to customize how strings are displayed by using the built-in text visualizer shown in Figure 4. The display setting for the ATL CComBSTR is shown below:

ATL::CComBSTR{
   preview  ([$e.m_str,su])
   stringview  ([$e.m_str,sub])
}

In both cases, the BSTR member variable is shown by using the $e identifier (which is a pseudo-variable for the object being displayed) followed by the m_str member variable. The difference between the preview and stringview settings is that the string format specifier – su instructs the debugger to display a string with double-quotes, and sub is a string with no quotation marks. Figure 4 shows the display differences.

Figure 4: Customizing stringview display

Advanced Debug Display

For types that have complex display requirements, the Visual C++ debugger supports the use of a conditional display using #if, #else, #switch, and #case statements. The COM VARIANT type is clearly a type that requires a conditional debugger display based on the data stored in the VARIANT, and the autoexp.dat definition, which is partially reproduced below, confirms this:

tagPROPVARIANT|tagVARIANT|PROPVARIANT|VARIANT{
   preview(
      #switch ($e.vt)
      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ;;  Base Types  ;;
      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      #case 0 ( #("Empty") ) ; VT_EMPTY
      #case 1 ( #("NULL") ) ; VT_NULL
      #case 2 ( #("I2 = ", $e.iVal) ) ; VT_I2
      #case 3 ( #("I4 = ", $e.lVal) ) ; VT_I4

Given the lack of clear documentation on the syntax supported by auotexp.dat and the poor debugging experience of the autoexp.dat expression, writing more complex expressions in C++ code will generally be more appealing. The use of C/C++ code to generate debug display information is accomplished via the $ADDIN command and the Expression Evaluator (EE) Add-In API. EE Add-In DLLs are C-style DLLs that expose functions of a specific signature, and a $ADDIN entry in autoexp.dat links a type to a DLL and function name used to display it.

The EE Add-In function is passed the memory of the object that needs to be displayed, a string buffer to store the results of the display, and a helper structure to retrieve additional information about the object being displayed. Once the EE Add-In has the memory address of the data structure that it is customized to display, it simply can cast the pointer to the correct type and use any set of string building and manipulation functions that it chooses to extract data from the type and place it in the return string. A sample EE Add-In DLL project ships with Visual C++ 2008.

Conclusion

Whether you’re a developer writing code as part of a small team, a framework developer producing code libraries for other members of your organization to consume, or a ISV producing components for the larger developer community, it is very important that the debug-time experience with the types you create is a good one. One of the fastest and most effective ways that the debug experience can be improved is by providing autoexp.dat entries that make inspecting the important member variables within your type easy.

For simple types, autoexp.dat entries are easy to create and can be completed in a couple of minutes. For more complex types, particularly those that are heavily based on templates, autoexp.dat now supports a rich syntax that can be used to provide detailed information. For types that have very specialized or complex display requirements, an add-in DLL can be written to process the data within a type using C/C++ and package it into a string for debugger consumption.

About the Author

Nick Wienholt is an independent Windows and .NET consultant based in Sydney. He is the author of Maximizing .NET Performance and co-author of A Programmers Introduction to C# 2.0 from Apress, and specialises in system-level software architecture and development, with a particular focus of performance, security, interoperability, and debugging.

Nick is a keen and active participant in the .NET community. He is the co-founder of the Sydney Deep .NET User group and writes technical articles for Australian Developer Journal, ZDNet, Pinnacle Publishing, CodeGuru, MSDN Magazine (Australia and New Zealand Edition) and the Microsoft Developer Network. An archive of Nick’s SDNUG presentations, articles, and .NET blog is available at www.dotnetperformance.com.

In recognition of his work in the .NET area, he was awarded the Microsoft Most Valued Professional Award from 2002 through 2007.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories