Using Visual Studio 2013 to write maintainable native visualizations (natvis)

Using Visual Studio 2013 to write maintainable native visualizations (natvis)

Rate This
  • Comments 18

In Visual Studio 2012 we introduced the ability to create visualizations for native types using natvis files.  Visual Studio 2013 contains several improvements that make it easier to author visualizations for classes that internally make use of collections to store items.   In this blog post I’ll show an example scenario that we wanted to improve, show you what you have to do in VS2012 to achieve the desired results, and then show you how the natvis authoring gets easier with VS2013 by exploring some of our new enhancements.

Example scenario

Let’s consider the following source code and suppose we are interesting in writing a visualizer for the CNameList class:

#include <vector>
using namespace std;

class CName
{
private:
    string m_first;
    string m_last;

public:
    CName(string first, string last) : m_first(first), m_last(last) {}

    void Print()
    { 
        wprintf(L"%s %s\n", (const char*) m_first.c_str(), (const char*) m_last.c_str());
    }
};

class CNameList
{
private:
    vector m_list;

public:
    CNameList() {}

    ~CNameList()
    {
        for (int i = 0; i < m_list.size(); i++)
        {
            delete m_list[i];
        }
        m_list.clear();
    }

    void AddName(string first, string last)
    {
        m_list.push_back(new CName(first, last));
    }


};

int _tmain(int argc, _TCHAR* argv[])
{
    CNameList presidents;
    presidents.AddName("George", "Washington");
    presidents.AddName("John", "Adams");
    presidents.AddName("Thomas", "Jefferson");
    presidents.AddName("Abraham", "Lincoln");

    return 0;
}

Our goal is to get ‘presidents’ to display like this:

 

CNameList visualizer for Visual Studio 2012

In Visual Studio 2012, it can be tricky for some to author the visualization for the CNameList class.  The most obvious natvis authoring:

<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
  <Type Name="CNameList">
    <Expand>
      <ExpandedItem>m_list</ExpandedItem>
    </Expand>
  </Type>

  <Type Name="CName">
    <DisplayString>{m_first} {m_last}</DisplayString>
    <Expand>
      <Item Name="First">m_first</Item>
      <Item Name="Last">m_last</Item>
    </Expand>
  </Type>
</AutoVisualizer>
 

Would display like this:

 

While the first and last names of the presidents are there, the view is a lot more cluttered than we may like.  Since we want to visualize the content of the CNameList object, rather than its implementation details, we may not care about the size or capacity of the internal vector, nor about the memory address of each CName object in the list, or the quotes around the first and last names to indicate that they are stored as separate strings.  With Visual Studio 2012, removing this clutter is possible, but it is rather cumbersome, and requires the visualization for CName and CNameList to be coupled with implementation details of the STL.  For example, in VS2012 we could get rid of the size and capacity of the vector, as well as the memory addresses of the CName objects, by replacing the visualizer for CNameList with this:

  <Type Name="CNameList">
    <Expand>
      <IndexListItems>
        <Size>m_list._Mylast - m_list._Myfirst</Size>
        <ValueNode>*m_list._Myfirst[$i]</ValueNode>
      </IndexListItems>
    </Expand>
  </Type>

And we could get rid of the quotes around the first and last names by replacing the CName visualizer with this, which uses the “,sb” format specifier to remove the quotes around the characters in the string: 

  <Type Name="CName">
    <DisplayString Condition="m_first._Myres &lt; m_first._BUF_SIZE &amp;&amp; m_last._Myres &lt; m_last._BUF_SIZE">{m_first._Bx._Buf,sb} {m_last._Bx._Buf,sb}</DisplayString>
    <DisplayString Condition="m_first._Myres &gt;= m_first._BUF_SIZE &amp;&amp; m_last._Myres &lt; m_last._BUF_SIZE">{m_first._Bx._Ptr,sb} {m_last._Bx._Buf,sb}</DisplayString>
    <DisplayString Condition="m_first._Myres &lt; m_first._BUF_SIZE &amp;&amp; m_last._Myres &gt;= m_last._BUF_SIZE">{m_first._Bx._Buf,sb} {m_last._Bx._Ptr,sb}</DisplayString>
    <DisplayString Condition="m_first._Myres &gt;= m_first._BUF_SIZE &amp;&amp; m_last._Myres &gt;= m_last._BUF_SIZE">{m_first._Bx._Ptr,sb} {m_last._Bx._Ptr,sb}</DisplayString>

    <Expand>
      <Item Condition="m_first._Myres &lt; m_first._BUF_SIZE" Name="First">m_first._Bx._Buf,sb</Item>
      <Item Condition="m_first._Myres &gt;= m_first._BUF_SIZE" Name="First">m_first._Bx._Ptr,sb</Item>
      <Item Condition="m_last._Myres &lt; m_last._BUF_SIZE" Name="Last">m_last._Bx._Buf,sb</Item>
      <Item Condition="m_last._Myres &gt;= m_last._BUF_SIZE" Name="Last">m_last._Bx._Ptr,sb</Item>
    </Expand>
  </Type>
 

While these visualizations certainly work in the sense that they yield the desired clutter-free output in the watch window, they require more work to write and maintain.  First, the visualizers for both CNameList and CName now take dependencies on private members of classes in the STL.  As implementation details the STL are subject to change, these visualizers are at risk of not working in a future version of Visual Studio if the STL implementation changes something that these entries depend on.  Furthermore, if CNameList is distributed as a header file that could potentially be included from any version Visual Studio, you might need to include a separate natvis entry for CName, for each implementation of the STL, then have to update all of them, any time in the future that the implementation of CName changes.

Furthermore, when the visualizer for the internal class has conditionals in it, the conditionals end up multiplying in ways that make the visualizer a mess.  For instance, the built-in visualizer for std::basic_string has two possible display string cases: 

<Type Name="std::basic_string&lt;char,*&gt;">
    <DisplayString Condition="_Myres &lt; _BUF_SIZE">{_Bx._Buf,s}</DisplayString>
    <DisplayString Condition="_Myres &gt;= _BUF_SIZE">{_Bx._Ptr,s}</DisplayString>
    <StringView Condition="_Myres &lt; _BUF_SIZE">_Bx._Buf,s</StringView>
    <StringView Condition="_Myres &gt;= _BUF_SIZE">_Bx._Ptr,s</StringView>
    <Expand>
        <Item Name="[size]">_Mysize</Item>
        <Item Name="[capacity]">_Myres</Item>
        <ArrayItems>
            <Size>_Mysize</Size>
            <ValuePointer Condition="_Myres &lt; _BUF_SIZE">_Bx._Buf</ValuePointer>
            <ValuePointer Condition="_Myres &gt;= _BUF_SIZE">_Bx._Ptr</ValuePointer>
        </ArrayItems>
    </Expand>
</Type>
 

However, because CName contains both a first name and a last name, there are now four cases, instead of two, based on whether the first and last names are contained in _Bx._Buf or _Bx._Ptr.  If we were to enhance CName to store middle names as well, now the visualizer would be up to eight cases, as the number of cases doubles for each new name you want to display. So we wanted to offer a cleaner way.

CNameList Visualizer for Visual Studio 2013

In Visual Studio 2013, you can achieve an uncluttered view of CNameList in the watch window by writing your visualizer like this:

<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
  <Type Name="CNameList">
    <Expand>
      <ExpandedItem>m_list,view(simple)na</ExpandedItem>
    </Expand>
  </Type>

  <Type Name="CName">
    <DisplayString>{m_first,sb} {m_last,sb}</DisplayString>
    <Expand>
      <Item Name="First">m_first,sb</Item>
      <Item Name="Last">m_last,sb</Item>
    </Expand>
  </Type>
</AutoVisualizer>

This view visualization works by taking advantage of three new natvis features in Visual Studio 2013: multiple views of an object, a format specifier to suppress memory addresses of pointers, and the ability of format specifiers to propagate across multiple natvis entries. Let’s examine all three next.

Multiple Object Views

In Visual Studio 2012, a <Type> entry can only describe one way to view an object.  For example, because the default view of a vector includes child nodes for the size and capacity, every natvis entry that wants to show a vector must either also include child nodes for the size and capacity or inline the vector visualizer into the visualizer of the type that uses the vector.

In Visual Studio 2013, each type still has only one default view, and it is now possible for a natvis entry to define additional views that can be accessed through an appropriate format specifier.  For example, the Visual Studio 2013 visualization of std::vector does so like this:

<Type Name="std::vector&lt;*&gt;">
    <DisplayString>{{ size={_Mylast - _Myfirst} }}</DisplayString>
    <Expand>
        <Item Name="[size]" ExcludeView="simple">_Mylast - _Myfirst</Item>
        <Item Name="[capacity]" ExcludeView="simple">_Myend - _Myfirst</Item>
        <ArrayItems>
            <Size>_Mylast - _Myfirst</Size>
            <ValuePointer>_Myfirst</ValuePointer>
        </ArrayItems>
    </Expand>
</Type>

The <DisplayString> and the <ArrayItems> elements are used always, while the “[size]” and “[capacity]” items are excluded from a view that has a name of “simple”. Normally, objects are displayed using the default view, which will show all the elements. However, the “,view” format specifier can be used to specify an alternate view, as shown this example of a simple vector of integers. Note that “vec,view(xxx)” behaves exactly the same as the default view because the natvis entry for vector does not contain any special behavior for a view of “xxx”.

 

If you want a natvis element to be added to, rather than removed from a particular view, you can use the “IncludeView” attribute, instead of “ExcludeView”. You may also specify a semi-colon delimited list of views in “IncludeView” and “ExcludeView” attributes, if you want the attribute to apply to a set of views, rather than just one. For example, this visualization will show the display text of “Alternate view” using either “,view(alternate)” or “,view(alternate2)”, and “Default View” in other cases.

  <Type Name="MyClass">
    <DisplayString IncludeView="alternate; alternate2">Alternate view </DisplayString>
    <DisplayString>Default View</DisplayString>
  </Type>

So, going back to our example, our CNameList visualizer takes advantage of the “simple” view defined in the “vector” visualizer to eliminate the clutter of the size and capacity nodes:

  <Type Name="CNameList">
    <Expand>
      <ExpandedItem>m_list,view(simple)na</ExpandedItem>
    </Expand>
  </Type>

Skipping Memory Addresses

Visual Studio 2013 adds a new format specifier, “,na”. When applied to a pointer, the “,na” format specifier causes the debugger to omit the memory address pointed to, while still retaining information about the object. For example:

In our CNameList example, we use the “,na” format specifier to hide the memory addresses of the CName objects, which are unimportant. Without the “,na” format specifier, hiding the memory addresses would have required copy-pasting and modifying the visualizer for std::vector to make it dereference the elements inside of the vector, like this:

  <Type Name="CNameList">
    <Expand>
      <IndexListItems>
        <Size>m_list._Mylast - m_list._Myfirst</Size>
        <ValueNode>*m_list._Myfirst[$i]</ValueNode>
      </IndexListItems>
    </Expand>
  </Type>

In our CNameList example, we use the “,na” format specifier to hide the memory addresses of the CName objects, which are unimportant. Without the “,na” format specifier, hiding the memory addresses would have required copy-pasting and modifying the visualizer for std::vector to make it dereference the elements inside of the vector, as illustrated here.

It should also be noted that the “,na” format specifier is not quite the same as the dereferencing operator “*”. Even though the “,na” format specifier will omit the memory address of the data being pointed to, any available symbolic information about that address will still be displayed. For example, in the function case, “*wmain” would be a syntax error, but “wmain,na” shows the module and signature of the “wmain” function, omitting the memory address. Similarly, “&myGlobal,na” still shows you that the pointer is pointing to the symbols “int myGlobal”. The “,na” format specifier can also be used on memory addresses in the middle of a function, as illustrated in the “(void*)eip,na” example. This can make the “,na” format specifier quite attractive for visualizing stack traces that have been logged inside of objects, for debugging purposes.

Propagating Format Specifiers

Even though the “,sb” format specifier already exists in Visual Studio 2012, authoring the CName visualizer like this does not work in VS2012:

  <Type Name="CName">
    <DisplayString>{m_first,sb} {m_last,sb}</DisplayString>
    <Expand>
      <Item Name="First">m_first,sb</Item>
      <Item Name="Last">m_last,sb</Item>
    </Expand>
  </Type>

The reason is that “m_first” is not actually a char*, but rather an std::basic_string. Because of this, Visual Studio 2012 actually obtains the format specifier for the underlying char* from the std::basic_string visualizer, not the CName visualizer. While the use of “m_first,sb” is still legal syntax, under Visual Studio 2012, the “,sb” in CName’s visualizer actually gets completely ignored.

In the meantime, because the visualizer for std::basic_string is designed to work for the common case, the std::basic_string uses “,s”, not “,sb”, causing the quotes to be included. Hence, while your intention was to get stripped out, they are actually still there . In Visual Studio 2012, the only workaround without changing std::basic_string, and potentially messing up other visualizations, not related to CName, is to inline the contents of std::basic_string into CName’s visualizer, so the string being used with “,sb” is actually a direct char*, rather than an std::basic_string.

In Visual Studio 2013, format specifiers used on visualized objects get merged with format specifiers of the object’s visualizer itself, rather than getting thrown out. In other words, in Visual Studio 2013, the “b” in “m_first,sb” propagates to the strings shown in the std::basic_string visualizer, allowing the quotes to be nicely and easily stripped out, without needing to modify or inline the visualizer for std::basic_string.

Another example of propagation of format specifiers is our new visualizer for CNameList. Even if the “,na” format specifier did exist in Visual Studio 2012, without the propagation of format specifiers, “m_list,na” would still not work, as the “,na” would simply be ignored due to std::vector’s visualizer not using “,na”. In Visual Studio 2013, the “,na” format specifier automatically propagates to the elements of the vector and things just work.

Yet another good example of propagation of format specifiers is displaying the elements of an integer collection in hexadecimal. The “,x” format specifier to display an integer in hex is already present in Visual Studio 2012, but only when applied directly to an integer value. When applied to a vector object, Visual Studio 2012 will simply ignore it, like this:

In Visual Studio 2012, showing the vector elements in hex would have required either modifying the visualizer for std::vector so that every vector would have its elements in hex, or toggling the global “hexadecimal display” option, which would cause every watch item to be formatted in hex, not just that one vector.

In Visual Studio 2013, “,x” simply propagates down to the children of the vector automatically, like this:

Other Visualization improvements

While the above features are all that is necessary to make our CNameList example work, there are a few other natvis-related improvements that have been asked for and are worth mentioning as well:

Using the final implementation name of a class inside a display string:

In Visual Studio 2013, a natvis entry for a base class may make use of the name of the object’s implementation class the $(Type) macro inside of a element. For example, if we have this source code:

class Room
{
private:
    int m_squareFeet;

public:
    Room() : m_squareFeet(100) {}

    virtual void Print() = 0;
};

class Bedroom : public Room
{
public:
    virtual void Print() { printf("Bedroom"); }

};

class LivingRoom : public Room
{
public:
    virtual void Print() { printf("Living room"); }

};

class DiningRoom : public Room
{
public:
    virtual void Print() { printf("Dining room"); }
};

int _tmain(int argc, _TCHAR* argv[])
{
    Bedroom br;
    LivingRoom lr;
    DiningRoom dr;

    br.Print();
    lr.Print();
    dr.Print();
}

We can write one visualizer for class “Room” like this:

<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
  <Type Name="Room">
    <DisplayString>{m_squareFeet}-square foot $(Type)</DisplayString>
  </Type>
</AutoVisualizer>

that will display which type of room we have, like this:

In Visual Studio 2012, achieving this display would have required creating a separate <Type> element for each type of room.

Note that the use of $(Type) is case-sensitive. $(TYPE) will not work. Also, the use of $(Type) requires the base class to contain at least one virtual function because, without the existence of a vtable, the debugger has no way to know what the object’s implementation class actually is.

Support for Circular Linked Lists

In Visual studio 2013, the <LinkedListItems> element adds support for circular lists that point back to the head of the list to indicate termination. For example, with the following source code:

class CircularList
{
private:
    struct Node
    {
        int m_value;
        Node* m_pNext;
    };

    Node* m_pFirst;

    Node* GetTail()
    {
        if (!m_pFirst)
            return NULL;

        Node* pNode = m_pFirst;
        while (pNode->m_pNext != m_pFirst)
            pNode = pNode->m_pNext;

        return pNode;
    }
public:
    CircularList() : m_pFirst(NULL) {}

    ~CircularList()
    {
        Node* pNode = m_pFirst;
        while (pNode != m_pFirst)
        {
            Node* pNext = pNode->m_pNext;
            delete pNode;

            pNode = pNext;
        }
    }

    void AddTail(int i)
    {
        Node* pNewNode = new Node();

        if (m_pFirst)
            GetTail()->m_pNext = pNewNode;
        else
            m_pFirst = pNewNode;

        pNewNode->m_value = i;
        pNewNode->m_pNext = m_pFirst;
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    CircularList list;
    list.AddTail(1);
    list.AddTail(2);
    list.AddTail(3);

	return 0;
}

We can display the value of ‘list’ with a simple element, like this:

<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
  <Type Name="CircularList">
    <Expand>
      <LinkedListItems>
        <HeadPointer>m_pFirst</HeadPointer>
        <NextPointer>m_pNext</NextPointer>
        <ValueNode>m_value</ValueNode>
      </LinkedListItems>
    </Expand>
  </Type>
</AutoVisualizer>

In Visual Studio 2013, the output is this:

In Visual Studio 2012, the list would be assumed to go on forever, since the “next” pointer of a list node is never NULL, thereby producing output like this:

Circular list support in Visual Studio 2013 only applies when an element’s “next” pointer points directly back to the head of the list. If a “next” pointer points back to a prior node in the list, that is not at the head, Visual Studio 2013 will still treat the list as continuing forever, just as Visual Studio 2012 does.

Because the “next” pointer expression does have access to fields in the underlying list object, the is no reasonable workaround for Visual Studio 2012.

In Closing

Visual Studio 2013 seeks to address the most common cases in which deficiencies in the natvis framework require compromises in the quality of the visualized view of an object and/or the maintainability of the natvis entries behind it. For feedback on this or any other diagnostics-related features please visit our MSDN forum and I also look forward to your comments below.

  • These are interesting new features, however, I simply don't understand why you don't revamp the entire native debugger visualizer infrastructure to make it more like the managed version, where you write _code_ that does arbitrary operations to create the visualizer result. Your purely declarative approach is simply not powerful enough to visualize a lot of custom containers that are used in real world projects.

    Please consider giving the native visualizers some serious attention for v.Next

  • The tricky part of executing arbitrary code to visualize an object is making sure that such code does not corrupt or alter the behavior of the debuggee process.  If the visualizer code hangs or crashes, the debuggee would be left in an unusable state, requiring you to restart your entire debug session each time this happens.  Because function evaluations under the debugger run with all other threads suspended, many common operations that would ordinarily be benign would run the risk of randomly deadlocking if it is necessary to acquire an internal lock that another thread, suspended by the debugger, already owns.

    In managed code, the same type of issues still apply, and there are lots of cases where evaluating a ToString() method can corrupt the debuggee process.  However, because the execution of all managed code is controlled by the .NET runtime, the CLR is usually able to restore the state of the program after a hang or unhandled exception - for example, the CLR will run finally blocks to release any locks that may have been acquired, etc.  In native code, with no controlling runtime, we cannot reliably recover from a function evaluation that hangs or crashes, like we can in managed.

    In short, a native visualization model that is built upon evaluating functions in the target process, similar to managed, would lead to frustrating situations in which the target process would appear to randomly crash or hang for no reason, forcing you to tear it down and start over again.  That is why we did not design native visualization this way, and that is why function calls (except intrinsics) are not supported inside of expressions in natvis files.

    Nevertheless, we are very much interested in improving the declarative natvis model to make it more expressive.  Obviously, it will never be able to handle every single case, but we are very interested in making it handle the more common patterns, allowing it to be useful for most types.  If you have an example of a type that you believe the declarative natvis model should be able to handle, but can't, please post a comment so we know about it and can consider making your scenario work in a future release.

  • These changes look real nice, but I don't know if I would bother if the visualizers are so separated from the code. How about a doc-comment style link from the source code to the .natvis file, so it could be source controlled along side everything else, and I don't have to pass files around the team?

    Sandboxed arbitrary execution would be real nice (something on top of ReadProcessMemory() would be both flakey and stupid slow, I would guess, but maybe something along the lines of Google's Native Client could host multiple executions in one process); but frankly better

  • I couldn't agree with Simon more - visualizers should be closer to the source code.

    Think about it - your favorite open source library will not come with a setup project that will install visualizers to [vsinstall or userdir]\...\Visualizers and copying them manually is not something we want to do (especially when working with multiple library versions).

    The same thing is with the internally developed code - even if I define a visualizer for my semi-useful class requiring everyone in the team to copy it to \Visualizers is unfeasible and unmaintainable (copy it every time the implementation changes?).

    This brings me to my point - .NET model has 2 advantages that could be implemented in the native debugger:

    1) The "visualizer" "travels" with the code/implementation

    2) You get it even with no source code since the information is embedded in the assembly

    I don't see why it would be so hard implement the listed features:

    1) for every type in mysource.h/.cpp scan the location of the file for mysource.natvis or \visualizers\mysource.natvis and load the visualizers

    2) you could event include the visualizers in the private pdb files (somehow?) and scan the pdb for visualizers

    This way it becomes a write once and forget feature while everyone benefits!

    Oh, and also give us DebuggerStepThroughAttribute and DebuggerNonUserCodeAttribute equivalents (pragmas?) and apply them to the STL and other library implementations (full "just my code" debugging support would be even better). Stepping into STL is annoying...

  • @Hrvoje: Good news! VC++2013 will have native "Just My Code"! Bad News: saying what is not "User Code' is a per install/user config file listing :/. (Would a #pragma be so hard?)

  • Hi,

    I am author of that extension:

    visualstudiogallery.msdn.microsoft.com/c7e02633-86d9-4262-b745-6cc647afb3a8

    First of all, thank you for new features.

    > If you have an example of a type that you believe the declarative natvis model should be able to handle, but can't, please post a comment so we know about it and can consider making your scenario work in a future release.

    I tried to make a visualizer for boost::property_tree and Boost Multi-index Containers on which it is based, but I did not manage to do that.

    Because I cannot access to helper functions and method would be cool to provide way to emulate it. Something like that: http://pastebin.com/z0Tt8ahS

    How also write native debugger visualizer for variadic templates?

  • @ Simon

    > How about a doc-comment style link from the source code to the .natvis file, so it could be source controlled along side everything else, and I don't have to pass files around the team?

    This bad because visualizers are big and this reduce readability of headers + only VS can read this type of comments. .natvis file could be also under SCM.

    @Hrvoje

    > Think about it - your favorite open source library will not come with a setup project that will install visualizers to [vsinstall or userdir]\...\Visualizers and copying them manually is not something we want to do (especially when working with multiple library versions).

    I think in future VS should support fetching visualizers from NuGet C++ package, so you can get library

    right away with debugger visualizers.

    > The same thing is with the internally developed code - even if I define a visualizer for my semi-useful class requiring everyone in the team to copy it to \Visualizers is unfeasible and unmaintainable (copy it every time the implementation changes?).

    VS just should support fetching all visualizers that included in VS solution when you run debugger session.

  • @simon

    Thanks. I had no idea we're getting native just my code.

    It seems we're getting:

    - .natvis - visualizers

    - .natstepfilter - step into filtering

    - .natjmc  - just my code

    Unfortunately, all of the improvements suffer from the same shortcomings we're discussing. So close, but so far... Hopefully till VS 2014 there will time to implement some usability improvements. One year cycles are like a blink of an eye for us c++ guys :)

  • The introduction of natvis files in VS2012 was a great improvement over the old autoexp.dat file. I’m happy to see you are continuing to improving the functionality in VS2013.

    I am missing one feature - the possibility to set the string length in DisplayString and StringView (or maybe the max length in case the string itself contains null characters). This would make it much more usable with strings that are not null terminated. This is the case with the UnicodeString class in the ICU library…

  • I agree with @ Simon,  that .natvis should get closer to code.

    So how about this model:

    1 in .h/.hpp

    #ifdef WITH_NATVIS // or some other ms-vc special stuff

    #include "foo.natvis"  // by including the file, the compiler can do some simple check, at least can detect data filelds mismatch

    #endif

    2 then when compile and link, just gather all the .natvis entries include in the source files and merge it to some single .exe.natvis along with .pdb or  just simply embeded in .pdb

    3 debugger just add the .exe.nativis to its collection, it's enough, no need to scan the .sln over and over

    This way, .natvis is better integrated with code, and without jaming other compiler that doesn't support it, and get better chance to get checked along with the code.

  • These are good improvements, thanks for working on this!

    Can you confirm that these now work with C++/CLI code too???? Please??? It would be awesome if I could stop maintaining natvis AND autoexp.dat simultaneously for my types.

  • My experiments today show that natvis visualizers are still not used when debugging in Mixed mode. Please allow these to work for mixed mode development.

  • The biggest thing I think is missing from the natvis framework is support for hash tables:

    1. There's no way to skip items in an array expansion, so the bucket list can't be represented without a whole lot of cruft.

    2. There's no way to merge an arbitrary number of item lists together, so closed-addressed hashing can't be represented without an extra tree level.

    3. There's no way to print the item key in the left column rather than as part of the display string.

    4. There's no way to sort items.

    1 and 2 can be worked around for tables whose items are backed by a linked list, which I suspect is why this wasn't in 2012, but it makes other hash table implementations impossible to effectively visualize, particularly for large collections.

  • Incidentally, I understand your desire to reduce the risk involved in writing visualizers, and I like that you've provided us with a framework that does that. But I think you might be taking the declarative-only, impossible-to-mess-things-up ethos too far.

    As a user, if I want to shoulder the responsibility of writing potentially unsafe visualizers, why do you want to stop me from doing that? If my debugger crashes because of a bad visualizer I wrote, then that's too bad, but it's something I can fix, particularly if you toss me an error message once a visualizer has taken more than 1 second to execute. A fundamentally incomplete and noncompletable visualizer framework, in contrast, is not something I can fix as a user.

    In other words: Give me back my rope. I promise not to blame you if I end up hanging myself a few times.

  • Sound good, but how about this easy case: Display a human readable date from COleDateTime. I see no way to do this in natvis.

    Instead of extending the existing model, it is a replacement. Why? Now I have to choose between two different debugging models, each has its disadvantages.

Page 1 of 2 (18 items) 12