使用 Natvis 框架在调试器中创建自定义C++对象的视图
Visual Studio Natvis 框架可以自定义本机类型在调试器变量窗口(例如局部变量和监视窗口)以及数据提示中显示的方式。 Natvis 可视化效果有助于在调试期间使你创建的类型更加可见。
Natvis 将 Visual Studio 早期版本中的 autoexp.dat 文件替换为 XML 语法、更好的诊断、版本控制以及多个文件支持。
备注
Natvis 自定义适用于类和结构,但不适用于 typedef。
Natvis 可视化效果
使用 Natvis 框架为创建的类型创建可视化规则,以便开发人员可以在调试期间更轻松地查看它们。
例如,下图显示了调试器窗口中类型为 Windows::UI::XAML::Controls::TextBox 的变量,而不应用任何自定义可视化效果。
高亮行显示的是 TextBox
类的 Text
属性。 复杂的类层次结构使得很难找到此属性。 调试器不知道如何解释自定义字符串类型,因此看不到文本框中保留的字符串。
应用 Natvis 自定义可视化工具规则时,同一个 TextBox
在变量窗口中看起来要简单得多。 类的重要成员一起显示,调试器显示自定义字符串类型的基础字符串值。
**
在 C++ 项目中使用 .natvis 文件
Natvis 使用 .natvis 文件来指定可视化规则。 .natvis 文件是扩展名为 .natvis 的 XML 文件。 Natvis 架构在 <VS 安装文件夹>\Xml\Schemas\1033\natvis.xsd中定义。
.natvis 文件的基本结构是由一个或多个 Type
元素表示的可视化条目。 每个 Type
元素的完全限定名称在其 Name
属性中指定。
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="MyNamespace::CFoo">
.
.
</Type>
<Type Name="...">
.
.
</Type>
</AutoVisualizer>
Visual Studio 在 <VS 安装文件夹>\Common7\Packages\Debugger\Visualizers 文件夹中提供了一些 .natvis 文件。 这些文件具有许多常见类型的可视化规则,并可用作为新类型编写可视化效果的示例。
将 .natvis 文件添加到C++项目
可以将 .natvis 文件添加到任何C++项目。
若要添加新的 .natvis 文件,请执行以下操作:
在 解决方案资源管理器中选择C++项目节点,然后选择 项目>添加新项,或右键单击项目并选择“添加>新项。
如果未看到所有项模板,请选择 显示所有模板。
在“添加新项 对话框中,选择 Visual C++>实用工具>调试器可视化文件(.natvis)。
命名文件,然后选择“添加”。
新文件将添加到 解决方案资源管理器,并在 Visual Studio 文档窗格中打开。
Visual Studio 调试器会自动加载 C++ 项目中的 .natvis 文件,默认情况下,在项目生成时,也会将它们包含在 .pdb 文件中。 如果调试生成的应用,则调试器会从 .pdb 文件加载 .natvis 文件,即使项目未打开也是如此。 如果不希望 .pdb中包含的 .natvis 文件,则可以将其从生成的 .pdb 文件中排除。
若要从 .pdb 中排除 .natvis 文件,请执行以下操作:
在解决方案资源管理器 中选择.natvis 文件,然后选择 属性 图标,或右键单击该文件并选择 属性。
按下“从生成中排除”旁边的下拉箭头并选择“是”,然后选择“确定”。
备注
对于调试可执行项目,请使用解决方案项添加任何 .natvis 文件,这些文件不在 .pdb中,因为没有可用的C++项目。
备注
从 .pdb 加载的 Natvis 规则 仅适用于 .pdb 引用的模块中的类型。 例如,如果 Module1.pdb 具有名为 Test
的类型的 Natvis 条目,则它仅适用于 Module1.dll中的 Test
类。 如果另一个模块还定义了名为 Test
的类,则 Module1.pdb Natvis 条目不适用于该类。
若要通过 VSIX 包安装和注册 .natvis 文件:
VSIX 包可以安装和注册 .natvis 文件。 无论安装在何处,所有已注册的 .natvis 文件都会在调试过程中自动加载。
在 VSIX 包中包含 .natvis 文件。 例如,对于以下项目文件:
<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="14.0"> <ItemGroup> <VSIXSourceItem Include="Visualizer.natvis" /> </ItemGroup> </Project>
在 source.extension.vsixmanifest 文件中注册 .natvis 文件:
<?xml version="1.0" encoding="utf-8"?> <PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011"> <Assets> <Asset Type="NativeVisualizer" Path="Visualizer.natvis" /> </Assets> </PackageManifest>
Natvis 文件位置
如果要将 .natvis 文件添加到用户目录或系统目录,则可以将其应用于多个项目。
按以下顺序评估 .natvis 文件:
所调试的 .pdb 文件中内嵌的所有的 .natvis 文件,除非加载的项目中存在同名文件。
加载的 C++ 项目或顶级解决方案中的所有 .natvis 文件。 此组包括所有加载C++项目,包括类库,但不包括其他语言的项目。
所有通过 VSIX 包安装和注册的 .natvis 文件。
- 特定于用户的 Natvis 目录(例如,%USERPROFILE%\Documents\Visual Studio 2022\Visualizers)。
- 特定于用户的 Natvis 目录(例如,%USERPROFILE%\Documents\Visual Studio 2019\可视化工具)。
- 系统范围的 Natvis 目录(<Microsoft Visual Studio 安装文件夹>\Common7\Packages\Debugger\Visualizers)。 此目录具有随 Visual Studio 一起安装的 .natvis 文件。 如果你有管理员权限,则可以将文件添加到此目录。
调试时修改 .natvis 文件
调试项目时,可以在 IDE 中修改 .natvis 文件。 在要调试的 Visual Studio 的同一实例中打开该文件,对其进行修改并保存。 保存文件后,“监视”和“局部变量”窗口就会随之更新,显示所做的更改。
还可以在要调试的解决方案中添加或删除 .natvis 文件,Visual Studio 添加或删除相关的可视化效果。
在调试时,无法更新嵌入在 .pdb 文件中的 .natvis 文件。
如果在 Visual Studio 外部修改 .natvis 文件,则更改不会自动生效。 若要更新调试器窗口,可以在 即时窗口 中重新执行 .natvisreload 命令。 然后更改生效,无需重启调试会话。
此外,使用 .natvisreload 命令将 .natvis 文件升级到较新版本。 例如,.natvis 文件可能被纳入了源代码管理中,并且你需要获取其他人最近所做的更改。
表达式和格式
Natvis 可视化效果使用C++表达式指定要显示的数据项。 除了调试器中C++表达式的增强和限制(如在上下文运算符(C++)中所述),请注意以下事项:
Natvis 表达式是在正在可视化的对象上下文中计算的,而不是当前堆栈帧。 例如,Natvis 表达式中的
x
是指对象中名为 x 的字段,而不是当前函数中名为 x 的局部变量。 虽然可以访问全局变量,但无法在 Natvis 表达式中访问局部变量。Natvis 表达式不允许函数计算或副作用。 函数调用和赋值运算符将被忽略。 由于 调试器内部函数 无副作用,因此可以从任何 Natvis 表达式自由调用它们,即使不允许其他函数调用也是如此。
若要控制表达式的显示方式,可以使用C++格式说明符中所述的任何格式说明符。 如果条目由 Natvis 在内部使用,格式说明符将被忽略,例如 ArrayItems 扩展中的
Size
表达式。
备注
由于 Natvis 文档是 XML,因此表达式不能直接使用 & 符号、大于号、小于号或移位运算符。 必须在项目正文和条件语句中对这些字符进行转义。 例如:
\<Item Name="HiByte"\>(byte)(_flags \>\> 24),x\</Item\>
\<Item Name="HiByteStatus" Condition="(_flags \& 0xFF000000) == 0"\>"None"\</Item\>
\<Item Name="HiByteStatus" Condition="(_flags \& 0xFF000000) != 0"\>"Some"\</Item\>
Natvis 视图
可以通过定义不同的 Natvis 视图,以不同的方式来显示类型。 例如,下面是 std::vector
的可视化效果,用于定义名为 simple
的简化视图。 DisplayString
和 ArrayItems
元素显示在默认视图和 simple
视图中,而 [size]
和 [capacity]
项不会显示在 simple
视图中。
<Type Name="std::vector<*>">
<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>
在“监视”窗口中,使用 ,view 格式说明符来指定替代视图。 简单视图显示为 vec,view(simple):
Natvis 错误
当调试器在可视化项中遇到错误时,它将忽略它们。 它要么以原始形式显示类型,要么选取另一个合适的可视化效果。 可以使用 Natvis 诊断来了解调试器为何忽略可视化项,并查看基础语法和分析错误。
若要启用 Natvis 诊断,请执行以下操作:
- 在“工具”>“选项”(或“调试”>“选项”)>“调试”>“输出窗口”下,将“Natvis 诊断消息(仅限 C++)”设置为“错误”、“警告”或“详细”,然后选择“确定”。
这些错误显示在 输出 窗口中。
Natvis 语法参考
可以在 Natvis 文件中使用以下元素和属性。
AutoVisualizer 元素
AutoVisualizer
元素是 .natvis 文件的根节点,包含命名空间 xmlns:
属性。
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
.
.
</AutoVisualizer>
AutoVisualizer
元素可以具有 Type、HResult、UIVisualizer和 CustomVisualizer 子元素。
Type 元素
基本 Type
如下例所示:
<Type Name="[fully qualified type name]">
<DisplayString Condition="[Boolean expression]">[Display value]</DisplayString>
<Expand>
...
</Expand>
</Type>
Type
元素指定:
应该用于哪种类型的可视化(
Name
属性)。该类型的对象的值是什么样的(
DisplayString
元素)。当用户在变量窗口中展开类型(
Expand
节点)时,该类型的成员应如下所示。
模板化类
Type
元素的 Name
属性接受星号 *
作为通配符,该通配符可用于模板化的类名。
在下面的示例中,无论对象是 CAtlArray<int>
还是 CAtlArray<float>
,都使用相同的可视化效果。 如果 CAtlArray<float>
有特定的可视化条目,它的优先级将高于通用的条目。
<Type Name="ATL::CAtlArray<*>">
<DisplayString>{{Count = {m_nSize}}}</DisplayString>
</Type>
可以使用宏$T 1、$T 2 等在可视化项中引用模板参数。 若要查找这些宏的示例,请参阅 Visual Studio 随附的 .natvis 文件。
可视化工具类型匹配
如果可视化项无法验证,则使用下一个可用的可视化效果。
可继承属性
可选 Inheritable
属性指定可视化效果是仅适用于基类型还是应用于基类型和所有派生类型。 Inheritable
的默认值为 true
。
在以下示例中,可视化效果仅适用于 BaseClass
类型:
<Type Name="Namespace::BaseClass" Inheritable="false">
<DisplayString>{{Count = {m_nSize}}}</DisplayString>
</Type>
优先级属性
可选 Priority
属性指定使用备用定义的顺序(如果定义无法分析)。 Priority
的可能值为:Low
、MediumLow
、Medium
、MediumHigh
和 High
。 默认值为 Medium
。 Priority
属性仅区分同一 .natvis 文件中的优先级。
以下示例首先分析与 2015 STL 匹配的条目。 如果分析失败,它将使用 STL 2013 版本的备用条目:
<!-- VC 2013 -->
<Type Name="std::reference_wrapper<*>" Priority="MediumLow">
<DisplayString>{_Callee}</DisplayString>
<Expand>
<ExpandedItem>_Callee</ExpandedItem>
</Expand>
</Type>
<!-- VC 2015 -->
<Type Name="std::reference_wrapper<*>">
<DisplayString>{*_Ptr}</DisplayString>
<Expand>
<Item Name="[ptr]">_Ptr</Item>
</Expand>
</Type>
可选属性
可以在任何节点上放置 Optional
属性。 如果可选节点内的子表达式无法分析,调试器将忽略该节点,但应用 Type
规则的其余部分。 在以下类型中,[State]
为非可选类型,但 [Exception]
是可选的。 如果 MyNamespace::MyClass
具有名为 _M_exceptionHolder
的字段,则同时显示 [State]
节点和 [Exception]
节点,但是如果没有 _M_exceptionHolder
字段,则只会显示 [State]
节点。
<Type Name="MyNamespace::MyClass">
<Expand>
<Item Name="[State]">_M_State</Item>
<Item Name="[Exception]" Optional="true">_M_exceptionHolder</Item>
</Expand>
</Type>
条件属性
可选 Condition
属性可用于许多可视化元素,并指定何时使用可视化规则。 如果条件属性内的表达式解析为 false
,则可视化规则不适用。 如果计算结果为 true
,或者没有 Condition
属性,则应用可视化效果。 可以将此属性用于可视化项中的 if-else 逻辑。
例如,以下可视化效果中有两个 DisplayString
的元素属于智能指针类型。 当 _Myptr
成员为空时,第一个 DisplayString
元素的条件将解析为 true
,以便显示窗体。 当 _Myptr
成员不为空时,条件的计算结果为 false
,并显示第二个 DisplayString
元素。
<Type Name="std::auto_ptr<*>">
<DisplayString Condition="_Myptr == 0">empty</DisplayString>
<DisplayString>auto_ptr {*_Myptr}</DisplayString>
<Expand>
<ExpandedItem>_Myptr</ExpandedItem>
</Expand>
</Type>
IncludeView 和 ExcludeView 属性
IncludeView
和 ExcludeView
属性指定要在特定视图中显示或不显示的元素。 例如,在 std::vector
的以下 Natvis 规范中,simple
视图不显示 [size]
和 [capacity]
项。
<Type Name="std::vector<*>">
<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>
可以对类型和单个成员使用 IncludeView
和 ExcludeView
属性。
Version 元素
Version
元素将可视化项的范围限定为特定模块和版本。 Version
元素有助于避免名称冲突、减少无意不匹配,并允许不同类型版本的不同可视化效果。
如果不同模块使用的通用头文件定义了一种类型,则仅当类型位于指定的模块版本中时,才会显示版本控制可视化效果。
在下面的例子中,该可视化仅适用于从版本 1.0 到 1.5 的 Windows.UI.Xaml.dll
中找到的 DirectUI::Border
类型。
<Type Name="DirectUI::Border">
<Version Name="Windows.UI.Xaml.dll" Min="1.0" Max="1.5"/>
<DisplayString>{{Name = {*(m_pDO->m_pstrName)}}}</DisplayString>
<Expand>
<ExpandedItem>*(CBorder*)(m_pDO)</ExpandedItem>
</Expand>
</Type>
不需要 Min
和 Max
。 它们是可选属性。 不支持通配符。
Name
属性采用 filename.ext格式,例如 hello.exe 或 some.dll。 不允许使用路径名称。
DisplayString 元素
DisplayString
元素指定要显示为变量值的字符串。 它接受与表达式混合的任意字符串。 大括号内的所有内容都将被解释为表达式。 例如,以下 DisplayString
条目:
<Type Name="CPoint">
<DisplayString>{{x={x} y={y}}}</DisplayString>
</Type>
表示 CPoint
类型的变量显示如下图所示:
在 DisplayString
表达式中,x
和 y
(CPoint
的成员)位于大括号内,因此会对它们的值进行求值。 该示例还介绍了如何用双层大括号({{
或 }}
)对大括号进行转义。
备注
DisplayString
元素是唯一接受任意字符串和大括号语法的元素。 所有其他可视化元素仅接受调试器可以计算的表达式。
StringView 元素
StringView
元素定义调试器可以发送到内置文本可视化工具的值。 例如,以下是针对 ATL::CStringT
类型的可视化效果:
<Type Name="ATL::CStringT<wchar_t,*>">
<DisplayString>{m_pszData,su}</DisplayString>
</Type>
CStringT
对象在变量窗口中显示,如以下示例所示:
添加 StringView
元素会告知调试器它可以将值显示为文本可视化效果。
<Type Name="ATL::CStringT<wchar_t,*>">
<DisplayString>{m_pszData,su}</DisplayString>
<StringView>m_pszData,su</StringView>
</Type>
在调试期间,可以选择变量旁边的放大镜图标,然后选择 文本可视化工具 以显示 m_pszData 指向的字符串。
表达式 {m_pszData,su}
包含C++格式说明符 su,以将值显示为 Unicode 字符串。 有关详细信息,请参阅 C++ 中的格式说明符。
Expand 元素
可选的 Expand
节点用于自定义当你在变量窗口中展开类型时,该可视化类型的子项。 Expand
节点接受定义子元素的子节点的列表。
如果未在可视化条目中指定
Expand
节点,子项将使用默认的展开规则。如果指定
Expand
节点且其下没有子节点,则类型在调试器窗口中不可展开。
Item 展开
Item
元素是 Expand
节点中最基本的常见元素。 Item
定义单个子元素。 例如,具有字段 top
、left
、right
和 bottom
的 CRect
类具有以下可视化项:
<Type Name="CRect">
<DisplayString>{{top={top} bottom={bottom} left={left} right={right}}}</DisplayString>
<Expand>
<Item Name="Width">right - left</Item>
<Item Name="Height">bottom - top</Item>
</Expand>
</Type>
在调试器窗口中,CRect
类型如以下示例所示:
CRect 使用 Item 元素扩展
调试器计算在 Width
和 Height
元素中指定的表达式,并显示变量窗口 值 列中的值。
调试器会自动为每个自定义扩展创建 [原始视图] 节点。 前面的屏幕截图显示展开的 [原始视图] 节点,以显示对象的默认原始视图与其 Natvis 可视化效果有何不同。 默认扩展为基类创建子树,并将基类的所有数据成员列为子类。
备注
如果项元素的表达式指向复杂类型,则 项 节点本身是可展开的。
ArrayItems 展开
使用 ArrayItems
节点让 Visual Studio 调试器将类型解释为数组并显示其各个元素。 std::vector
的可视化效果是一个很好的示例:
<Type Name="std::vector<*>">
<DisplayString>{{size = {_Mylast - _Myfirst}}}</DisplayString>
<Expand>
<Item Name="[size]">_Mylast - _Myfirst</Item>
<Item Name="[capacity]">(_Myend - _Myfirst)</Item>
<ArrayItems>
<Size>_Mylast - _Myfirst</Size>
<ValuePointer>_Myfirst</ValuePointer>
</ArrayItems>
</Expand>
</Type>
在变量窗口中展开时,std::vector
显示了它的各个元素:
ArrayItems
节点必须具有:
- 一个
Size
表达式(必须计算为整数),以便调试器了解数组的长度。 - 指向第一个元素的
ValuePointer
表达式(必须是非void*
元素类型的指针)。
数组下限的默认值为 0。 若要覆盖该值,请使用 LowerBound
元素。 Visual Studio 随附的 .natvis 文件有示例。
备注
可以将 []
运算符(例如 vector[i]
)与使用 ArrayItems
的任何单维数组可视化一起使用,即使类型本身(例如 CATLArray
)不允许此运算符。
还可以指定多维数组。 在这种情况下,调试器需要稍多一些信息才能正确显示子元素:
<Type Name="Concurrency::array<*,*>">
<DisplayString>extent = {_M_extent}</DisplayString>
<Expand>
<Item Name="extent">_M_extent</Item>
<ArrayItems Condition="_M_buffer_descriptor._M_data_ptr != 0">
<Direction>Forward</Direction>
<Rank>$T2</Rank>
<Size>_M_extent._M_base[$i]</Size>
<ValuePointer>($T1*) _M_buffer_descriptor._M_data_ptr</ValuePointer>
<LowerBound>0</LowerBound>
</ArrayItems>
</Expand>
</Type>
Direction
指定数组是按行主顺序还是按列主要顺序排列。Rank
指定数组的排名。Size
元素接受隐式$i
参数,该参数将替换为维度索引以查找该维度中数组的长度。- 在前面的示例中,表达式
_M_extent.M_base[0]
应提供第 0 个维度的长度,_M_extent._M_base[1]
第一个维度等。
- 在前面的示例中,表达式
LowerBound
指定数组每个维度的下限。 对于多维数组,可以指定使用隐式$i
参数的表达式。$i
参数将替换为维度索引,以查找该维度中数组的下限。- 在前面的示例中,所有维度都将从 0 开始。 但是,如果
($i == 1) ? 1000 : 100
为下限,第 0 个维度将从 100 开始,第一个维度将从 1000 开始。- 例如
[100, 1000], [100, 1001], [100, 1002], ... [101, 1000], [101, 1001],...
- 例如
- 在前面的示例中,所有维度都将从 0 开始。 但是,如果
下面是二维 Concurrency::array
对象在调试器窗口中的外观:
IndexListItems 展开
仅当数组元素在内存中连续布局时,才能使用 ArrayItems
扩展。 调试器只需递增其指针即可进入下一个元素。 如果你需要操作值节点的索引,可以使用 IndexListItems
节点。 下面是 IndexListItems
节点的可视化处理:
<Type Name="Concurrency::multi_link_registry<*>">
<DisplayString>{{size = {_M_vector._M_index}}}</DisplayString>
<Expand>
<Item Name="[size]">_M_vector._M_index</Item>
<IndexListItems>
<Size>_M_vector._M_index</Size>
<ValueNode>*(_M_vector._M_array[$i])</ValueNode>
</IndexListItems>
</Expand>
</Type>
ArrayItems
和 IndexListItems
之间的唯一区别是 ValueNode
,它期望带有隐式 $i
参数的第 i 个元素的完整表达式。
备注
可以将 []
运算符(例如 vector[i]
)与使用 IndexListItems
的任何单维数组可视化一起使用,即使类型本身(例如 CATLArray
)不允许此运算符。
LinkedListItems 展开
如果可视化类型表示链表,则调试器可以使用 LinkedListItems
节点显示其子节点。 CAtlList
类型的以下可视化效果使用 LinkedListItems
:
<Type Name="ATL::CAtlList<*,*>">
<DisplayString>{{Count = {m_nElements}}}</DisplayString>
<Expand>
<Item Name="Count">m_nElements</Item>
<LinkedListItems>
<Size>m_nElements</Size>
<HeadPointer>m_pHead</HeadPointer>
<NextPointer>m_pNext</NextPointer>
<ValueNode>m_element</ValueNode>
</LinkedListItems>
</Expand>
</Type>
Size
元素引用列表的长度。 HeadPointer
指向第一个元素,NextPointer
引用下一个元素,ValueNode
引用项的值。
调试器在 LinkedListItems
节点元素的上下文中计算 NextPointer
和 ValueNode
表达式,而不是父列表类型。 在前面的示例中,CAtlList
有一个 CNode
类(在 atlcoll.h
中找到),该类是链接列表的节点。 m_pNext
和 m_element
是该 CNode
类的字段,而不是 CAtlList
类的字段。
ValueNode
可以留空,也可以使用 this
来引用 LinkedListItems
节点本身。
CustomListItems 展开
通过 CustomListItems
扩展,可以编写用于遍历数据结构(如哈希表)的自定义逻辑。 使用 CustomListItems
来可视化数据结构,这些结构可以使用 C++ 表达式计算所需的一切,但不太适合 ArrayItems
、IndexListItems
或 LinkedListItems
的框架。
可以使用 Exec
通过扩展中定义的变量和对象在 CustomListItems
扩展内执行代码。 可以将逻辑运算符、算术运算符和赋值运算符与 Exec
一起使用。 不能使用 Exec
来评估函数,除非是由C++表达式计算器支持的 调试器内部函数。
下面的 CAtlMap
可视化工具是一个很好的例子,其中的 CustomListItems
用得很恰当。
<Type Name="ATL::CAtlMap<*,*,*,*>">
<AlternativeType Name="ATL::CMapToInterface<*,*,*>"/>
<AlternativeType Name="ATL::CMapToAutoPtr<*,*,*>"/>
<DisplayString>{{Count = {m_nElements}}}</DisplayString>
<Expand>
<CustomListItems MaxItemsPerView="5000" ExcludeView="Test">
<Variable Name="iBucket" InitialValue="-1" />
<Variable Name="pBucket" InitialValue="m_ppBins == nullptr ? nullptr : *m_ppBins" />
<Variable Name="iBucketIncrement" InitialValue="-1" />
<Size>m_nElements</Size>
<Exec>pBucket = nullptr</Exec>
<Loop>
<If Condition="pBucket == nullptr">
<Exec>iBucket++</Exec>
<Exec>iBucketIncrement = __findnonnull(m_ppBins + iBucket, m_nBins - iBucket)</Exec>
<Break Condition="iBucketIncrement == -1" />
<Exec>iBucket += iBucketIncrement</Exec>
<Exec>pBucket = m_ppBins[iBucket]</Exec>
</If>
<Item>pBucket,na</Item>
<Exec>pBucket = pBucket->m_pNext</Exec>
</Loop>
</CustomListItems>
</Expand>
</Type>
TreeItems 展开
如果可视化类型表示一棵树,则调试器可以使用 TreeItems
节点遍历树,并显示其子节点。 下面是使用 TreeItems
节点的 std::map
类型的可视化效果:
<Type Name="std::map<*>">
<DisplayString>{{size = {_Mysize}}}</DisplayString>
<Expand>
<Item Name="[size]">_Mysize</Item>
<Item Name="[comp]">comp</Item>
<TreeItems>
<Size>_Mysize</Size>
<HeadPointer>_Myhead->_Parent</HeadPointer>
<LeftPointer>_Left</LeftPointer>
<RightPointer>_Right</RightPointer>
<ValueNode Condition="!((bool)_Isnil)">_Myval</ValueNode>
</TreeItems>
</Expand>
</Type>
语法类似于 LinkedListItems
节点。 LeftPointer
、RightPointer
和 ValueNode
是在树节点类的上下文中计算的。 ValueNode
可以留空或使用 this
来引用 TreeItems
节点本身。
ExpandedItem 展开
ExpandedItem
元素通过显示基类或数据成员的属性来生成聚合子视图,就像它们是可视化类型的子级一样。 调试器计算指定的表达式,并将结果的子节点追加到可视化类型的子列表中。
例如,智能指针类型 auto_ptr<vector<int>>
通常显示为:
要查看矢量的值,就必须在变量窗口中穿过 _Myptr
成员,向下深入两个级别。 通过添加 ExpandedItem
元素,可以从层次结构中消除 _Myptr
变量,并直接查看向量元素:
<Type Name="std::auto_ptr<*>">
<DisplayString>auto_ptr {*_Myptr}</DisplayString>
<Expand>
<ExpandedItem>_Myptr</ExpandedItem>
</Expand>
</Type>
以下示例演示如何聚合派生类中基类的属性。 假设 CPanel
类派生自 CFrameworkElement
。 ExpandedItem
节点可视化效果会将这些属性追加到 CPanel
类的子列表中,而不是重复基 CFrameworkElement
类中的属性。
<Type Name="CPanel">
<DisplayString>{{Name = {*(m_pstrName)}}}</DisplayString>
<Expand>
<Item Name="IsItemsHost">(bool)m_bItemsHost</Item>
<ExpandedItem>*(CFrameworkElement*)this,nd</ExpandedItem>
</Expand>
</Type>
此处需要 格式说明符(关闭派生类的可视化匹配)。 否则,表达式 *(CFrameworkElement*)this
将导致再次应用 CPanel
可视化效果,因为默认的可视化效果类型匹配规则会将其视为最合适的可视化效果。 使用 nd 格式说明符指示调试器使用基类可视化效果;如果基类没有可视化效果,则使用默认扩展。
Synthetic Item 展开
虽然 ExpandedItem
元素通过消除层次结构来提供更平淡的数据视图,但 Synthetic
节点会相反。 它允许你创建不是表达式结果的人工子元素。 人工元素可以具有其自己的子元素。 在以下示例中,Concurrency::array
类型的可视化效果使用 Synthetic
节点向用户显示诊断消息:
<Type Name="Concurrency::array<*,*>">
<DisplayString>extent = {_M_extent}</DisplayString>
<Expand>
<Item Name="extent" Condition="_M_buffer_descriptor._M_data_ptr == 0">_M_extent</Item>
<ArrayItems Condition="_M_buffer_descriptor._M_data_ptr != 0">
<Rank>$T2</Rank>
<Size>_M_extent._M_base[$i]</Size>
<ValuePointer>($T1*) _M_buffer_descriptor._M_data_ptr</ValuePointer>
</ArrayItems>
<Synthetic Name="Array" Condition="_M_buffer_descriptor._M_data_ptr == 0">
<DisplayString>Array members can be viewed only under the GPU debugger</DisplayString>
</Synthetic>
</Expand>
</Type>
内部扩展
可从表达式调用的自定义内部函数。 <Intrinsic>
元素必须附带调试器组件,该组件通过 IDkmIntrinsicFunctionEvaluator140 接口实现函数。 有关实现自定义内部函数的详细信息,请参阅 实现 NatVis 自定义内部函数。
<Type Name="std::vector<*>">
<Intrinsic Name="size" Expression="(size_t)(_Mypair._Myval2._Mylast - _Mypair._Myval2._Myfirst)" />
<Intrinsic Name="capacity" Expression="(size_t)(_Mypair._Myval2._Myend - _Mypair._Myval2._Myfirst)" />
<DisplayString>{{ size={size()} }}</DisplayString>
<Expand>
<Item Name="[capacity]" ExcludeView="simple">capacity()</Item>
<Item Name="[allocator]" ExcludeView="simple">_Mypair</Item>
<ArrayItems>
<Size>size()</Size>
<ValuePointer>_Mypair._Myval2._Myfirst</ValuePointer>
</ArrayItems>
</Expand>
</Type>
HResult 元素
借助 HResult
元素,你可以自定义显示在调试器窗口中的 HRESULT 信息。 HRValue
元素必须包含要自定义的 HRESULT 的 32 位值。 HRDescription
元素包含要显示在调试器窗口中的信息。
<HResult Name="MY_E_COLLECTION_NOELEMENTS">
<HRValue>0xABC0123</HRValue>
<HRDescription>No elements in the collection.</HRDescription>
</HResult>
UIVisualizer 元素
UIVisualizer
元素向调试器注册图形可视化工具插件。 图形可视化工具创建一个对话框或其他接口,该对话框或其他接口以与其数据类型一致的方式显示变量或对象。 可视化工具插件必须编写为 VSPackage,并且必须公开调试器可以使用的服务。 .natvis 文件包含插件的注册信息,例如其名称、公开服务的全局唯一标识符(GUID),以及它可以可视化的类型。
下面是 UIVisualizer 元素的示例:
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<UIVisualizer ServiceId="{5452AFEA-3DF6-46BB-9177-C0B08F318025}"
Id="1" MenuName="Vector Visualizer"/>
<UIVisualizer ServiceId="{5452AFEA-3DF6-46BB-9177-C0B08F318025}"
Id="2" MenuName="List Visualizer"/>
.
.
</AutoVisualizer>
ServiceId
-Id
属性对用来标识UIVisualizer
。ServiceId
是可视化工具包公开的服务的 GUID。 如果服务提供多个,则Id
是区分可视化工具的唯一标识符。 在前面的示例中,同一可视化工具服务提供了两个可视化工具。MenuName
属性定义要在调试器中放大镜图标旁边的下拉列表中显示的可视化工具名称。 例如:
.natvis 文件中定义的每种类型都必须显式列出任何可以显示它的 UI 可视化工具。 调试器将类型项中的可视化工具引用与已注册的可视化工具匹配。 例如,std::vector
的以下类型项引用前面的示例中的 UIVisualizer
。
<Type Name="std::vector<int,*>">
<UIVisualizer ServiceId="{5452AFEA-3DF6-46BB-9177-C0B08F318025}" Id="1" />
</Type>
可在用于查看内存中位图的图像监视扩展中看到 UIVisualizer
的示例。
CustomVisualizer 元素
CustomVisualizer
是一个扩展点,它指定一个 VSIX 扩展,用于在 Visual Studio Code 中控制可视化效果。 有关编写 VSIX 扩展的详细信息,请参阅 Visual Studio SDK。
编写自定义视觉化器比编写 XML Natvis 定义要复杂得多,但这样你不必受限于 Natvis 的支持与否。 自定义可视化工具可以访问完整的调试器扩展性 API 集,这些 API 可以查询和修改调试对象进程或与 Visual Studio 的其他部分进行通信。
可以在 CustomVisualizer
元素上使用 Condition
、IncludeView
和 ExcludeView
属性。
局限性
Natvis 自定义适用于类和结构体,但不适用于类型定义(typedef)。
Natvis 不支持基元类型的可视化工具(例如,int
、bool
)或指向基元类型的指针。 在此方案中,一个选项是使用适合你的用例的 格式说明符。 例如,如果在代码中使用 double* mydoublearray
,则可以在调试器的 监视 窗口中使用数组格式说明符,例如表达式 mydoublearray, [100]
,其中显示了前 100 个元素。