Quantcast
Channel: Marius Bancila's Blog » VS2012
Viewing all articles
Browse latest Browse all 8

Visual Studio 2012 Debugger Visualizer for CPtrArray

$
0
0

CPtrArray is a nasty MFC container that should not be used. However, if you deal with legacy code you may not have a choice and have to work with it. Unfortunately, the Visual Studio debugger is not able to display its elements, since these are pointers to void and that can be anything. In this post I will explain how to write a visualizer for Visual Studio 2012 to address this problem.

Overview

Visual Studio 2012 has introduced a new framework for writing debugger visualizers for C++ types. This replaces the old autoexp.dat (that you might be familiar with). The new framework offers xml syntax, better diagnostics, versioning and multiple file support.

Visualizers are defined in XML files with extension .natvis. These visualizers are loaded each time the debugger starts. That means if you make a change to visualizers, it is not necessary to re-start Visual Studio, just re-start the debugger (for instance detach and re-attach the debugger to the process you debug). These files can be located under one of these locations:

  • %VSINSTALLDIR%\Common7\Packages\Debugger\Visualizers (requires admin access)
  • %USERPROFILE%\My Documents\Visual Studio 2012\Visualizers\
  • VS extension folders

You can read how to write visualizers in these articles:

Writing the visualizer

There are two things you must do to enable Visual Studio debugger to display CPtrArrays in a nice way.

The first step is to define a type derived from CPtrArray. This type will not be used in code, but will allow the visualizer to figure what is the type of the array elements.

template <typename T>
class CPtrArrayT : public CPtrArray
{
};

The second step is to create a .natvis file (let’s call it mfc.natvis) under %USERPROFILE%\My Documents\Visual Studio 2012\Visualizers\ with the following content:

<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
  
  <Type Name="CPtrArrayT&lt;*&gt;">
    <DisplayString>{{CPtrArray m_nSize={m_nSize}}}</DisplayString> 
    <Expand>
      <Item Name="m_nSize">m_nSize</Item>
      <Item Name="m_nMaxSize">m_nMaxSize</Item>
      <Item Name="m_nGrowBy">m_nGrowBy</Item>
      <IndexListItems>
        <Size>m_nSize</Size>
        <ValueNode>($T1*)m_pData[$i]</ValueNode>
      </IndexListItems>
    </Expand>      
  </Type>
  
</AutoVisualizer>

And that’s all. Let’s see how this works. Assume we have the following code:

struct FOO
{
   int      a;
   double   b;
   CString  c;
};

void TestArray()
{
   CPtrArray arr;

   FOO* f1 = new FOO;
   f1->a = 1;
   f1->b = 1.1;
   f1->c = "one";

   FOO* f2 = new FOO;
   f2->a = 2;
   f2->b = 2.2;
   f2->c = "two";

   FOO* f3 = new FOO;
   f3->a = 3;
   f3->b = 3.3;
   f3->c = "three";

   arr.Add(f1);
   arr.Add(f2);
   arr.Add(f3);

   delete f1;
   delete f2;
   delete f3;
}

In the Watch window cast the pointer to the array object to CPtrArray<T>. This is where the template type defined above is used. Even though your array is not an instance of CPtrArray<T> it will still work since the derived type does not add anything.

(CPtrArrayT<FOO>*)&arr,nd

vscptrarrayvis1
As you can see in the screenshot, the content of the CPtrArray is nicely expanded.

You probably noticed the nd specifier in the watch expression. This specifier (probably standing for “no derived”) displays only the base class info of the object, and not the derived parts (see Format Specifiers in C++). Without this specifier, when the MFC symbols are loaded (for mfc110xx.dll) the array is not visualized correctly.
vscptrarrayvis2

A simpler hard-coded solution

If you don’t want (or cannot) add the generic type CPtrArray<T> you can still accomplish the same, but with some drawbacks.

In this case the .natvis file should look like this:

<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
  
  <Type Name="CPtrArray">
    <DisplayString>{{CPtrArray m_nSize={m_nSize}}}</DisplayString> 
    <Expand>
      <Item Name="m_nSize">m_nSize</Item>
      <Item Name="m_nMaxSize">m_nMaxSize</Item>
      <Item Name="m_nGrowBy">m_nGrowBy</Item>
      <IndexListItems>
        <Size>m_nSize</Size>
        <ValueNode>(TYPE*)m_pData[$i]</ValueNode>
      </IndexListItems>
    </Expand>      
  </Type>
  
</AutoVisualizer>

You must change the placeholder TYPE with the actual type of the array elements in ValueNode. Then, all you have to do is add the object in the watch window.

However, the big disadvantage of this solution is that all the CPtrArrays in your code are treated in the same manner, as storing elements of type TYPE*. That means if you want to watch arrays of different types, you have to stop the debugger, change the visualizer and attach again. It is impossible to watch arrays that store different element types in the same debugging session.


Viewing all articles
Browse latest Browse all 8

Trending Articles