使用 Natvis 框架在调试器中创建 C++ 对象的自定义视图

Visual Studio Natvis 框架可以自定义本机类型在调试器变量窗口(例如 局部变量监视 以及 数据提示 窗口)中显示的方式。 Natvis 的可视化功能可以让你创建的类型在调试期间更加直观清晰。

Natvis 替换了 Visual Studio 早期版本中的 autoexp.dat 文件,提供了 XML 语法、更好的诊断功能、版本控制功能以及多文件支持功能。

备注

Natvis 自定义适用于类和结构,但不能适用于 typedef。

Natvis 可视化效果

你可以使用 Natvis 框架为自己创建的类型创建可视化规则,让开发人员在调试过程中更轻松地查看这些类型。

例如,下图显示的类型 Windows::UI::Xaml::Controls::TextBox 的变量在调试器窗口中未应用任何自定义可视化。

TextBox 默认可视化

高亮行显示的是 TextBox 类的 Text 属性。 由于类的层次结构很复杂,因此很难找到这个属性。 调试器不知道如何解释自定义字符串类型,所以你看不到文本框中的字符串。

如果应用了 Natvis 自定义可视化工具规则,那么在变量窗口中,同样的TextBox看起来就简单得多。 类的重要成员会显示在一起,并且调试器会显示自定义字符串类型的基础字符串值。

使用可视化工具的 TextBox 数据

在 C++ 项目中使用 .natvis 文件

Natvis 使用 .natvis 文件指定可视化效果规则。 .natvis 文件是具有 .natvis 扩展名的 XML 文件。 Natvis 架构在 %VSINSTALLDIR%\Xml\Schemas\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 在 %VSINSTALLDIR%\Common7\Packages\Debugger\Visualizers 文件夹中提供了一些 .natvis 文件 。 这些文件具有许多通用类型的可视化规则,可以充当有关编写新类型的可视化效果的示例。

将 .natvis 文件添加到 C++ 项目

可以将 .natvis 文件添加到任何 C++ 项目。

若要添加新的 .natvis 文件,请执行以下操作:

  1. 在“解决方案资源管理器”中选择 C++ 项目节点,然后选择“项目” > “添加新项”,或右键单击项目并选择“添加” > “新项”。

  2. 在“添加新项”对话框中,选择“Visual C++” > “实用工具” > “调试器可视化文件(.natvis)”。

  3. 命名文件,然后选择“添加”。

    新文件会添加到“解决方案资源管理器”,并在 Visual Studio 文档窗格中打开。

Visual Studio 调试器会自动在 C++ 项目中加载 .natvis 文件,默认情况下,还会在生成项目时,在 .pdb 文件中包含它们。 在调试生成的应用时,调试器会从 .pdb 文件加载 .natvis 文件,即使你没有打开该项目也是如此。 如果不想在 .pdb 中包含 .natvis 文件,可以从生成的 .pdb 文件中将其排除。

.pdb 中排除 .natvis 文件:

  1. 解决方案资源管理器 中选择 .natvis 文件,然后选择 属性 图标,或右键单击该文件并选择 属性

  2. 按下 从生成中排除 旁边的箭头并选择 ,然后选择 确定

备注

如果是调试可执行的项目,则使用解决方案项来添加 .pdb 中不包含的 .natvis 文件,因为没有可用的 C++ 项目。

备注

.pdb 加载的 Natvis 规则仅适用于 .pdb 引用的模块中的类型。 例如,如果 Module1.pdb 包含一个名为 Test 的类型的 Natvis 条目,则它仅适用于 Module1.dll 中的 Test 类。 如果另一个模块也定义了一个名为 Test 的类,则 Module1.pdb 的 Natvis 条目不适用于它。

若要通过 VSIX 包安装并注册 .natvis 文件,请执行以下操作:

VSIX 包可以安装和注册 .natvis 文件。 无论它们安装在何处,所有注册的 .natvis 文件都会在调试过程中自动提取。

  1. 将 .natvis 文件包含在 VSIX 包中。 例如,对于以下项目文件:

    <?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>
    
  2. 在 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 文件:

  1. 所调试的 .pdb 文件中内嵌的所有的 .natvis 文件,除非加载的项目中存在同名文件。

  2. 加载的 C++ 项目或顶级解决方案中的所有 .natvis 文件。 这包括所有已加载的 C++ 项目(包括类库),但不包括其他语言的项目。

  3. 任何通过 VSIX 包安装和注册的 .natvis 文件。

  1. 特定于用户的 Natvis 目录 (例如, %USERPROFILE%\Documents\Visual Studio 2017\Visualizers)。
  1. 特定于用户的 Natvis 目录 (例如, %USERPROFILE%\Documents\Visual Studio 2019\Visualizers)。
  1. 系统级 Natvis 目录 ( %VSINSTALLDIR%\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 &gt;&gt; 24),x</Item>
<Item Name="HiByteStatus" Condition="(_flags &amp; 0xFF000000) == 0">"None"</Item>
<Item Name="HiByteStatus" Condition="(_flags &amp; 0xFF000000) != 0">"Some"</Item>

Natvis 视图

你可以定义不同的 Natvis 视图,以便用不同的方式来显示类型。 例如,下面是 std::vector 的一个可视化效果,定义了一个名为 simple 的简化视图。 默认视图和 simple 视图中都显示了 DisplayStringArrayItems 元素,但 simple 视图中却未显示 [size][capacity] 项。

<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>

监视 窗口中,使用 ,view 格式说明符来指定替代视图。 simple 视图将显示为 vec,view(simple)

具有简单视图的监视窗口

Natvis 错误

当调试器遇到可视化条目中的错误时,会忽略它们。 要么以原始格式显示类型,要么选择其他合适的可视化效果。 你可以使用 Natvis 的诊断功能来了解调试器忽略可视化条目的原因,还可以查看基础语法和分析错误。

若要启用 Natvis 诊断,请执行以下操作:

  • 在“工具” > “选项”(或“调试” > “选项”)>“调试” > “输出窗口”下,将“Natvis 诊断消息(仅限 C++)”设置为“错误”、“警告”或“详细” ,然后选择“确定” 。

错误会出现在“输出”窗口中。

Natvis 语法参考

AutoVisualizer 元素

AutoVisualizer 元素是 .natvis 文件的根节点,并包含命名空间 xmlns: 属性。

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

AutoVisualizer 元素可以具有 TypeHResultUIVisualizerCustomVisualizer 子级。

Type 元素

基本 Type 如下例所示:

<Type Name="[fully qualified type name]">
  <DisplayString Condition="[Boolean expression]">[Display value]</DisplayString>
  <Expand>
    ...
  </Expand>
</Type>

Type 元素指定:

  1. 应使用可视化效果的类型(Name 特性)。

  2. 该类型的对象的值是什么样的( DisplayString 元素)。

  3. 用户在变量窗口中展开类型时,该类型的成员应当以什么形式显示(Expand 节点)。

模板化类

Type 元素的 Name 特性接受一个星号 * 作为可用于模板化类名的通配符。

在以下示例中,无论对象是 CAtlArray<int> 还是 CAtlArray<float>,都使用了相同的可视化效果。 如果 CAtlArray<float> 有特定的可视化条目,它的优先级将高于通用的条目。

<Type Name="ATL::CAtlArray&lt;*&gt;">
    <DisplayString>{{Count = {m_nSize}}}</DisplayString>
</Type>

可以使用 $T1 和 $T2 这样的宏,在可视化条目中引用模板参数。 有关这些宏的示例,请参阅 Visual Studio 随附的 .natvis 文件。

可视化工具类型匹配

如果无法验证某个可视化条目,则使用下一个可用的可视化效果。

可继承的特性

可选的 Inheritable 属性用于指定,一个可视化效果是仅适用于一个基类型,还是适用于一个基类型和所有的派生类型。 Inheritable 的默认值为 true

在下面的示例中,可视化效果仅适用于 BaseClass 类型:

<Type Name="Namespace::BaseClass" Inheritable="false">
    <DisplayString>{{Count = {m_nSize}}}</DisplayString>
</Type>

优先级特性

如果某个定义的分析失败,可选的 Priority 属性会指定使用备用定义的顺序。 Priority 的值包括:LowMediumLowMediumMediumHighHigh。 默认值为 MediumPriority属性只区分同一个 .natvis 文件中的优先级。

下面的示例会首先分析与 2015 STL 匹配的条目。 如果分析失败,就会使用 STL 的 2013 版本的备用条目:

<!-- VC 2013 -->
<Type Name="std::reference_wrapper&lt;*&gt;" Priority="MediumLow">
     <DisplayString>{_Callee}</DisplayString>
    <Expand>
        <ExpandedItem>_Callee</ExpandedItem>
    </Expand>
</Type>

<!-- VC 2015 -->
<Type Name="std::reference_wrapper&lt;*&gt;">
    <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&lt;*&gt;">
  <DisplayString Condition="_Myptr == 0">empty</DisplayString>
  <DisplayString>auto_ptr {*_Myptr}</DisplayString>
  <Expand>
    <ExpandedItem>_Myptr</ExpandedItem>
  </Expand>
</Type>

IncludeView 和 ExcludeView 特性

IncludeViewExcludeView 属性用于指定是否在特定视图内显示元素。 例如,在下面的 Natvis std::vector 规范中,simple 视图不显示 [size][capacity] 项。

<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>

可以针对类型和各个成员使用 IncludeViewExcludeView 属性。

Version 元素

Version 元素用于将可视化条目的范围限定在特定模块和版本内。 Version 元素有助于避免名称冲突、减少无意间的不匹配,还允许在不同的类型版本中使用不同的可视化效果。

如果某个由不同模块使用的公共头文件定义了一个类型,则只有当该类型位于指定的模块版本中时,版本化的可视化效果才会显示。

在下面的示例中,可视化效果只适用于在 Windows.UI.Xaml.dll 版本 1.0 到 1.5 中找到的 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>

不需要 MinMax。 它们是可选特性。 不支持通配符。

Name 特性的格式为 filename.ext,例如 hello.exe 或 some.dll 。 不允许使用路径名。

DisplayString 元素

DisplayString 元素用于指定要显示为变量值的字符串。 它接受混合了表达式的任意字符串。 大括号内的所有内容都将被解释为表达式。 例如,以下 DisplayString 条目:

<Type Name="CPoint">
  <DisplayString>{{x={x} y={y}}}</DisplayString>
</Type>

表示 CPoint 类型的变量显示如下图所示:

使用 DisplayString 元素

DisplayString 表达式中,属于 CPoint 成员的 xy 位于大括号内,因此它们的值会被计算。 该示例还介绍了如何用双层大括号({{}})对大括号进行转义。

备注

DisplayString 元素是唯一接受任意字符串和大括号语法的元素。 所有其他可视化元素只接受调试器可以计算的表达式。

StringView 元素

StringView 元素用于定义一个值,调试器可以将该值发送给内置的文本可视化工具。 例如,假设 ATL::CStringT 类型有以下可视化效果:

<Type Name="ATL::CStringT&lt;wchar_t,*&gt;">
  <DisplayString>{m_pszData,su}</DisplayString>
</Type>

CStringT 对象将显示在一个变量窗口中,如下例所示:

CStringT DisplayString 元素

添加 StringView 元素会告诉调试器,它可以将值显示为文本可视化效果。

<Type Name="ATL::CStringT&lt;wchar_t,*&gt;">
  <DisplayString>{m_pszData,su}</DisplayString>
  <StringView>m_pszData,su</StringView>
</Type>

在调试过程中,可以选择变量旁的放大镜图标,然后选择“文本可视化工具”以显示 m_pszData 指向的字符串。

使用 StringView 可视化工具的 CStringT 数据

表达式 {m_pszData,su} 包含一个 C++ 格式说明符 su,用于将值显示为 Unicode 字符串。 有关详细信息,请参阅 C++ 中的格式说明符

Expand 元素

可选的 Expand 节点用于自定义当你在变量窗口中展开类型时,该可视化类型的子项。 Expand 节点接受用于定义子元素的子节点列表。

  • 如果未在可视化条目中指定 Expand 节点,子项将使用默认的展开规则。

  • 如果指定的 Expand 节点下面没有子节点,类型就无法在调试器窗口中展开。

Item 展开

Item 元素是 Expand 节点中最基本、最常见的元素。 Item 用于定义单个子元素。 例如,一个包含 topleftrightbottom 字段的 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 类型如以下示例所示:

具有 Item 元素扩展的 CRect

调试器将计算 WidthHeight 元素中指定的表达式,然后在变量窗口的 列中显示值。

调试器会为每个自定义展开自动创建 [Raw View] 节点。 上面的屏幕截图中的 [Raw View] 节点是展开的,显示了对象的默认原始视图与其 Natvis 可视化效果的区别。 默认展开会为基类创建一个子树,并将基类的所有数据成员以子项的形式列出。

备注

如果项元素的表达式指向一个复杂类型,则“项”节点本身可展开。

ArrayItems 展开

使用 ArrayItems 节点,让 Visual Studio 调试器将类型解释为一个数组并显示其各个元素。 std::vector 的可视化效果是一个很好的示例:

<Type Name="std::vector&lt;*&gt;">
  <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 扩展的 std::vector

ArrayItems 节点必须具有:

  • 用于使调试器了解数组长度的 Size 表达式(必须计算为整数)。
  • 一个 ValuePointer 表达式,指向第一个元素(必须为非 void* 元素类型的指针)。

数组下限的默认值为 0。 要修改此值,请使用 LowerBound 元素。 Visual Studio 随附的 .Natvis 文件中提供了示例。

备注

可以使用 [] 运算符(例如 vector[i])以及任何使用了 ArrayItems 的一维数组可视化效果,即使该类型本身(例如 CATLArray)不允许使用此运算符。

还可以指定多维数组。 在这种情况下,调试器需要稍多一些信息即可正确显示子元素:

<Type Name="Concurrency::array&lt;*,*&gt;">
  <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>
    </ArrayItems>
  </Expand>
</Type>
  • Direction 指定数组是采用行优先顺序还是列优先顺序。
  • Rank 指定数组的秩。
  • Size 元素接受将其替换为维索引以查找该维度中数组的长度的隐式 $i 参数。 在前面的示例中,表达式 _M_extent.M_base[0] 应指定第 0 个维度的长度,_M_extent._M_base[1] 应指定第 1 个维度的长度,依此类推。

下面是调试器窗口中显示的一个二维 Concurrency::array 对象:

具有 ArrayItems 扩展的二维数组

IndexListItems 展开

仅当数组元素在内存中连续排列时,才能使用 ArrayItems 展开。 调试器只需递增其指针,就可以获取下一个元素。 如果你需要操作值节点的索引,可以使用 IndexListItems 节点。 下面是 IndexListItems 节点的可视化处理:

<Type Name="Concurrency::multi_link_registry&lt;*&gt;">
  <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>

ArrayItemsIndexListItems 之间的唯一区别是 ValueNode,它期望带有隐式元素 $i 参数的第 i 个元素的完整表达式。

备注

可以使用 [] 运算符(例如 vector[i])以及任何使用了 IndexListItems 的一维数组可视化效果,即使该类型本身(例如 CATLArray)不允许使用此运算符。

LinkedListItems 展开

如果可视化类型表示一个链接列表,则调试器可以通过使用 LinkedListItems 节点显示其子级。 CAtlList 类型的以下可视化效果使用 LinkedListItems

<Type Name="ATL::CAtlList&lt;*,*&gt;">
  <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 节点元素(而不是父列表类型)环境中计算 NextPointerValueNode 表达式。 在前面的示例中,CAtlList 有一个 CNode 类(位于 atlcoll.h 中),它是链接列表的节点。 m_pNextm_elementCNode 类(而不是 CAtlList 类)的字段。

ValueNode 可以保留为空或使用 this 来引用 LinkedListItems 节点本身。

CustomListItems 展开

借助 CustomListItems 展开,你可以编写自定义逻辑,用于遍历哈希表等数据结构。 使用 CustomListItems 来可视化数据结构,这些数据结构可以使用 C++ 表达式进行所有运算,但不太适合 ArrayItemsIndexListItemsLinkedListItems 模式。

借助在展开内定义的变量和对象,你可以在 CustomListItems 展开中使用 Exec 来执行内部代码。 可以将逻辑运算符、算术运算符和赋值运算符与 Exec 一起使用。 无法使用 Exec 计算函数,除了 C++ 表达式计算器支持的调试器内部函数

下面的 CAtlMap 可视化工具是一个很好的例子,其中的 CustomListItems 用得很恰当。

<Type Name="ATL::CAtlMap&lt;*,*,*,*&gt;">
    <AlternativeType Name="ATL::CMapToInterface&lt;*,*,*&gt;"/>
    <AlternativeType Name="ATL::CMapToAutoPtr&lt;*,*,*&gt;"/>
    <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&lt;*&gt;">
  <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 节点。 LeftPointerRightPointerValueNode 是在树节点类的上下文中计算的。 ValueNode 可以保留为空或使用 this 来引用 TreeItems 节点本身。

ExpandedItem 展开

通过将基类或数据成员的属性显示为可视化类型的子项,ExpandedItem 元素生成了一个聚合的子视图。 调试器将计算指定的表达式,并将结果的子节点附加到该可视化类型的子列表中。

例如,智能指针类型 auto_ptr<vector<int>> 通常显示为:

auto_ptr<vector<int>> 默认扩展

要查看矢量的值,就必须在变量窗口中穿过 _Myptr 成员,向下深入两个级别。 通过添加 ExpandedItem 元素,就可以从层次结构中去除 _Myptr 变量并直接查看矢量元素:

<Type Name="std::auto_ptr&lt;*&gt;">
  <DisplayString>auto_ptr {*_Myptr}</DisplayString>
  <Expand>
    <ExpandedItem>_Myptr</ExpandedItem>
  </Expand>
</Type>

auto_ptr<vector<int>> ExpandedItem 扩展

下面的示例介绍了如何将基类的属性聚合到一个派生类中。 假设 CPanel 类派生自 CFrameworkElementExpandedItem 节点的可视化会将基 CFrameworkElement 类的属性附加到 CPanel 类的子列表中,而不是重复这些属性。

<Type Name="CPanel">
  <DisplayString>{{Name = {*(m_pstrName)}}}</DisplayString>
  <Expand>
    <Item Name="IsItemsHost">(bool)m_bItemsHost</Item>
    <ExpandedItem>*(CFrameworkElement*)this,nd</ExpandedItem>
  </Expand>
</Type>

关闭派生类的可视化匹配的 nd 格式说明符肯定在这。 否则,表达式 *(CFrameworkElement*)this 会导致再次应用 CPanel 可视化效果,因为默认可视化类型匹配规则认为它是最适合的选项。 使用 nd 格式说明符指示调试器使用基类可视化效果或默认扩展(如果基类没有可视化效果)。

Synthetic Item 展开

ExpandedItem 元素通过消除层次结构提供更简单的数据视图,Synthetic 节点则恰好相反。 它允许你创建人工子元素(不是表达式的结果)。 人工元素可以具有其自己的子元素。 在下面的示例中, Concurrency::array 类型的可视化效果使用 Synthetic 节点向用户显示诊断消息:

<Type Name="Concurrency::array&lt;*,*&gt;">
  <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>

具有 Synthetic 元素扩展的 Concurrency::Array

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 属性对用来标识 UIVisualizerServiceId 是可视化工具包公开的服务的 GUID。 如果服务提供了多个可视化工具,Id 是用来区分它们的唯一标识符。 在上面的示例中,同一个可视化工具服务提供了两个可视化工具。

  • MenuName 属性用于定义可视化工具名称,该名称会显示在调试器的放大镜图标旁边的下拉列表中。 例如:

    UIVisualizer 菜单快捷菜单

.natvis 文件中定义的每种类型都必须明确列出能够显示它的所有 UI 可视化工具。 调试器会将类型条目中的可视化工具引用与注册的可视化工具相匹配。 例如,下面的 std::vector 类型条目引用的是前一个示例中的 UIVisualizer

<Type Name="std::vector&lt;int,*&gt;">
  <UIVisualizer ServiceId="{5452AFEA-3DF6-46BB-9177-C0B08F318025}" Id="1" />
</Type>

可在用于查看内存中位图的图像监视扩展中看到 UIVisualizer 的示例。

CustomVisualizer 元素

CustomVisualizer 是一个扩展点,用于指定你编写的 VSIX 扩展,以便在 Visual Studio 代码中控制可视化效果。 有关编写 VSIX 扩展的更多信息,请参阅 Visual Studio SDK

编写自定义可视化工具比 XML Natvis 定义要费事得多,但 Natvis 在支持方面的限制对你没有影响。 自定义可视化工具有权访问所有的调试器扩展性 API,因此可以查询和修改调试对象进程,也可以与 Visual Studio 的其他部分通信。

可以在 CustomVisualizer 元素上使用 ConditionIncludeViewExcludeView 属性。

限制

Natvis 自定义适用于类和结构,但不能适用于 typedef。

对于基元类型(例如 intbool)或指向基元类型的指针,Natvis 不支持可视化工具。 在此方案中,一种选择是使用适合于用例的格式说明符。 例如,如果在代码中使用 double* mydoublearray,则可以在调试器的“监视”窗口中使用数组格式说明符,如显示前 100 个元素的表达式 mydoublearray, [100]