Since recently, I'm working in C++ with COM technology, which means using SAFEARRAY objects. One of the first challenges is seeing the contents of such an object: in the watch-window, such an object looks as follows:
I can relate to that, but I do prefer showing such an array as a one-liner (this would make it easier to compare two of them), so I created the file "C:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\Packages\Debugger\Visualizers\ZSAFEARRAY.natvis".
The reason for that is basic: when performing a .natvisreload the native visualisers in that particular directory are handled in alphabetical order, based on filename, so using a name, starting with a "Z", causes that file to be handled after "windows.natvis", overwriting the already existing visualiser.
But what can I do to show a list of information as a one-liner? Well, the "windows.natvis" contains the following entry:
<Type Name="APP_LOCAL_DEVICE_ID">
<Expand HideRawView="true">
<IndexListItems>
<Size>32</Size>
<ValueNode>value[$i],nvoxb</ValueNode>
</IndexListItems>
</Expand>
</Type>
So, I've written this very easy piece of code:
APP_LOCAL_DEVICE_ID test; // for natvis reasons: check "ListItems"
test.value[0] = 0x01;
test.value[1] = 0x02;
test.value[2] = 0x04;
test.value[3] = 0x08;
test.value[4] = 0x10;
test.value[5] = 0x11;
test.value[6] = 0xAB;
test.value[7] = 0xFF;
Showing that variable in the watch-window yields the following result:

=> This is more or less what I want.
So, very naïvely, I've modified the SAFEARRAY item in my ZSAFEARRAY.natvis entry as follows:
<Expand>
<IndexListItems>
<!--<Size>32</Size>-->
<Size>rgsabound[cDims - $i - 1].cElements</Size>
<ValueNode>value[$i],nvoxb</ValueNode>
</IndexListItems>
<ArrayItems>
<Direction>Backward</Direction>
<Rank>cDims</Rank>
<Size>rgsabound[cDims - $i - 1].cElements</Size>
<LowerBound>rgsabound[cDims - $i - 1].lLbound</LowerBound>
<ValuePointer Condition="(fFeatures & 0x0080) &&
(((unsigned *)&(*this))[-1] == 0x02)">((signed short *)(*this).pvData)</ValuePointer>
...
<ValuePointer Condition="(fFeatures & 0x0080) &&
(((unsigned *)&(*this))[-1] == 0x17)">((unsigned int *)(*this).pvData)</ValuePointer>
...
</ArrayItems>
</Expand>
No way this can work: the $i variable in <Size>rgsabound[cDims - $i - 1].cElements</Size> and the <ValueNode> tag have a completely different meaning!
Although I see why it can't work, I have no idea how to make it work.
Does anybody have an idea how to get SAFERARRAY items as a one-liner in a natvis?
I'm working with Visual Studio 2022.
When making a few bold assumptions, creating a visualizer that roughly accomplishes what you want is easy. The following works if you know that your SAFEARRAY
cDims == 1)NUL character<Type Name="tagSAFEARRAY">
<AlternativeType Name="SAFEARRAY"/>
<DisplayString>array = [{pvData,s}]</DisplayString>
</Type>
This is using the s format specifier to interpret the pointer as a C-style narrow character string and produces the following output:
The format specifiers are supported in Natvis expressions as well as the debugger. If you want to experiment with them, you can add a watch in Visual Studio's debugger and get immediate feedback on the formatting effects (try sa->pvData,sb, for example).
While one-dimensional SAFEARRAYs are very common, the last two assumptions feel overly restrictive. Dropping them is possible, but not without giving up some control over the representation. The limiting factor is that the Natvis infrastructure prevents operations with side effects. To the best of my knowledge, it is (currently) impossible to "construct" visual representations in a visualizer. I haven't found a way to build a <DisplayString> out of arbitrary data (which is what the "on one line" requirement is ultimately asking for).
There is a workaround, though: When you omit the <DisplayString> element, Visual Studio's debugger will conjure a top-level representation, by somehow concatenating the child <Expand> element, into a (possibly truncated) one-liner.
Here is a visualizer that employs the <ArrayItems> expansion, leaving the top-level representation to the debugger:
<Type Name="tagSAFEARRAY">
<AlternativeType Name="SAFEARRAY"/>
<Expand>
<ArrayItems>
<Size>rgsabound[0].cElements</Size>
<LowerBound>rgsabound[0].lLbound</LowerBound>
<ValuePointer>(char*)pvData</ValuePointer>
</ArrayItems>
</Expand>
</Type>
This produces the following debugger output:
A few points worth noting:
<Size> element ensures that all elements are included.<LowerBound> (and lLbound) is an interface concept only; internally, arrays are zero-based.pvData is of type void* (an incompleete type); <ValuePointer> needs a complete type, hence the (char*)-cast.The <ArrayItems> expansion is sufficient for this use case. For more complex container item traversal, Natvis provides the <CustomListItems> expansion. It supports local state management (via <Variable> elements) and control flow (<Loop>, <Break>, If, etc.). It is useful for situations where the standard expansions do not suffice.
For purposes of illustration, here is an alternative visualizer that replicates the <ArrayItems> expansion using a (manual) <CustomListItems> expansion:
<Type Name="tagSAFEARRAY">
<AlternativeType Name="SAFEARRAY"/>
<Expand>
<CustomListItems>
<Variable Name="index" InitialValue="0"/>
<Loop>
<Break Condition="index == rgsabound[0].cElements"/>
<Item>*((char*)pvData + index),c</Item>
<Exec>++index</Exec>
</Loop>
</CustomListItems>
</Expand>
</Type>
The output is very similar to the previous:
One notable difference is that the "lower bound" is ignored in the items' expansion. If this is desired, adding a Name="[{rgsabound[0].lLbound + index}]" attribute to the <Item>-tag does that.
This covers the basic tools available. A "real" visualizer would have to be more sophisticated. The item expansion especially needs to be guarded with appropriate Condition attributes to enforce the assumptions. But that is for a different Q&A.
Authoring Natvis visualizers is challenging. While the documentation has substantially improved over time, there's still a lot of information missing, that needs to be discovered via trial and error. Plus, there's next to no debugging support. Here are a few tips you can use to improve the situation.
Visual Studio has the unique feature of surfacing Natvis diagnostics. To enable it go to Tools -> Options..., expand the Debugging node and navigate to Output Window. Find the setting called "Natvis diagnostic messages (C++ only)" and set it to "Verbose".
This will inevitably produce a lot of noise in the Debug output. When you see a wall of text, the important information is close to the top.
To quickly iterate on a visualizer it is often easiest to create a new project. A bare-bones console application (generated by the Windows Desktop Wizard) is usually sufficient. Having a project allows you to add a Natvis to it (right-click the project in the Solution Explorer and select Add -> New Item..., then expand the Visual C++ node and select Utility, and choose "Debugger visualization file (.natvis)").
Adding the .natvis file to a project has a few advantages: It can readily be opened in Visual Studio for editing. The built-in editor sports full Intellisense and code analysis support (sourced from "natvis.xsd"), which I found to be incredibly helpful. In addition, the MSBuild system generates the appropriate /NATVIS linker options to dump the .natvis into the PDB.
For reference, here are the source files I initially used to approach this issue:
SafearrayNatvis.cpp
#include <Windows.h>
#include <iterator>
int main()
{
auto const& HELLO = "Hello, World!";
auto sa = ::SafeArrayCreateVector(VT_UI1, 42, std::size(HELLO));
char* pdata = nullptr;
::SafeArrayAccessData(sa, reinterpret_cast<void**>(&pdata));
std::copy(std::begin(HELLO), std::end(HELLO), pdata);
return 0;
}
This program creates an empty, one-dimensional SAFEARRAY, with an arbitrary lower bound. The return 0; statement isn't strictly required, but it makes for a nice place to set a breakpoint on.
ZSAFEARRAY.natvis
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="tagSAFEARRAY">
<AlternativeType Name="SAFEARRAY"/>
<DisplayString>moose</DisplayString>
</Type>
</AutoVisualizer>
This is the generated template with a single <Type> element added. Its <DisplayString> child element contains an arbitrary text (moose). I'm using this in lieu of proper Natvis debugging support.