为 C++ 实现 NatVis 自定义内部函数
在本文中,你将了解在 NatVis 可视化效果中实现自定义内部函数的准则。 有关 NatVis 的详细信息,请参阅 创建C++对象的自定义视图。
语法
NatVis 文件可以使用以下语法定义内部函数:
<Intrinsic Name="Func" Expression="arg + 1">
<Parameter Name="arg" Type="int" />
</Intrinsic>
定义存在后,任何调试器表达式都可能会像调用任何其他函数一样调用该函数。 例如,使用前面的 NatVis 定义,表达式 Func(3)
的计算结果为 4。
<Intrinsic>
元素可能出现在文件级别或 <Type>
元素中,并且在任何其他元素之前出现。 在 <Type>
中定义的内置函数是定义该类型的成员函数,而在 <Type>
外部定义的内置函数是定义全局函数。 例如:
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="std::vector<*>">
<Intrinsic Name="size" Expression="_Mypair._Myval2._Mylast - _Mypair._Myval2._Myfirst" />
<Intrinsic Name="capacity" Expression="_Mypair._Myval2._Myend - _Mypair._Myval2._Myfirst" />
</Type>
<Intrinsic Name="LOWORD" Expression="arg & 0xFFFF">
<Parameter Name="arg" Type="unsigned int" />
</Intrinsic>
</AutoVisualizer>
在前面的示例中,size()
和 capacity()
定义为 std::vector
类的成员函数,因此每当表达式计算 vector.size()
时,它实际上将计算 vector._Mypair._Myval2._Mylast - vector._Mypair._Myval2._Myfirst
,而不是执行 func-eval。
同样,LOWORD(0x12345678)
返回 0x5678
,也不需要执行 func-eval。
有关另一个示例,请参阅 内部扩展。
使用内部函数的准则
使用内部函数时,请注意以下准则:
可以将内部函数重载,既可以通过 PDB 定义的函数重载,也可以通过彼此重载。
当内部函数与具有相同名称和参数列表的 PDB 定义的函数冲突时,内部函数将获胜。 如果存在等效的内部函数,则无法对 PDB 函数进行 func-eval 操作。
无法获取内部函数的地址;只能调用它。
内部成员函数必须属于相应类型的默认视图,才能在表达式中求值。 例如,具有
IncludeView
约束的类型项不能指定内部函数。可以从任何表达式(包括 NatVis 表达式)调用内部函数。 内部函数也可以相互调用。 但是,目前不支持使用递归的内部函数。 例如:
<!-- OK --> <Intrinsic Name="Func2" Expression="this->Func3(arg + 1)"> <Parameter Name="arg" Type="int" /> </Intrinsic> <Intrinsic Name="Func3" Expression="arg + 1"> <Parameter Name="arg" Type="int"/> </Intrinsic> <!-- Unsupported --> <Intrinsic Name="Func2" Expression="this->Func3(arg + 1)"> <Parameter Name="arg" Type="int" /> </Intrinsic> <Intrinsic Name="Func3" Expression="Func2(arg + 1)"> <Parameter Name="arg" Type="int"/> </Intrinsic> <!-- Also unsupported--> <Intrinsic Name="Fib" Expression="(n <= 1) ? 1 : Fib(n - 1) + Fib(n - 2)"> <Parameter Name="n" Type="int"/> </Intrinsic>
默认情况下,假定内部函数是无副作用的。 也就是说,可以在禁止副作用的上下文中调用它们,并且不允许实现表达式包含副作用。
定义可以通过在声明中指定
SideEffect
属性来替代此行为。 如果函数被标记为具有副作用,则允许实现表达式中的副作用。 但是,不再允许在禁止副作用的上下文中调用函数。当两个内部函数的定义相互冲突(同名、同一签名)时,最后一个函数会赢(在同一文件中)。
不同文件中,优先级较高的文件实例会被选择(项目目录优先于用户目录,用户目录优先于安装目录)。 如果高优先级定义包含不分析的表达式,则忽略该定义,并改用下一个最高优先级定义。
实现内在函数的准则
内部函数支持两种可能的实现形式:
基于表达式的
NatVis 文件定义一个表达式,该表达式的计算结果为函数的返回值。 表达式可以使用传递给它的任何参数,这些参数声明为
<Parameter>
元素。 类中定义的函数也假定为“实例”函数,还可以访问“this”指针。基于扩展的
NatVis 文件提供有关调用调试器扩展以实际评估函数的说明。 调试器扩展具有对 Concord API 的完全访问权限,并且能够执行 NatVis 表达式中无法执行的任务。
若要提供基于扩展的实现,<Intrinsic>
元素应省略 Expression
属性,而是提供 SourceId
、LanguageId
、Id
和 ReturnType
属性,如以下示例中所示:
<Intrinsic Name="MyFunc" SourceId="a665fa54-6e7d-480e-a80b-1fc1202e9646" LanguageId="3a12d0b7-c26c-11d0-b442-00a0244a1dd2" Id="1000" ReturnType="double">
<Parameter Type="int" />
<Parameter Type="int" />
<Parameter Type="int" />
</Intrinsic>
若要实现函数,调试器扩展必须使用与 NatVis 文件中 <Intrinsic>
元素的相应值匹配的 LanguageId
和 SourceId
筛选器来实现 IDkmIntrinsicFunctionEvaluator140
接口。 调用函数时,调用将转换为组件的 Execute()
方法:
STDMETHOD(Execute)(
_In_ Evaluation::IL::DkmILExecuteIntrinsic* pExecuteIntrinsic,
_In_ Evaluation::DkmILContext* pILContext,
_In_ Evaluation::IL::DkmCompiledILInspectionQuery* pInspectionQuery,
_In_ const DkmArray<Evaluation::IL::DkmILEvaluationResult*>& Arguments,
_In_opt_ DkmReadOnlyCollection<Evaluation::DkmCompiledInspectionQuery*>* pSubroutines,
_Out_ DkmArray<Evaluation::IL::DkmILEvaluationResult*>* pResults,
_Out_ Evaluation::IL::DkmILFailureReason* pFailureReason
);
该组件通过 Arguments
参数接收每个参数的字节。 如果有问题的函数是成员函数,则 this
指针首先出现,后跟显式参数。 该组件应通过在 pResults
中分配单元素数组来返回结果,从而存储返回值的字节。
使用以下准则实现函数:
“混合和匹配”这两种实现形式是非法的。 也就是说,不能同时包含表达式和源 ID。
允许为基于表达式的实现指定返回类型,但不是必需的。 如果指定了返回类型,则表达式的返回类型必须与它完全匹配(不允许隐式转换)。 如果未指定返回类型,则从表达式推断返回类型。 任何基于扩展的实现都必须在 NatVis 文件中声明返回类型。
允许对其他内部函数的非递归调用。 不允许递归。
如果函数具有副作用,则必须在声明中指定
SideEffect="true"
。 基于表达式的实现如果在表达式中产生副作用,且没有声明该函数具有副作用,则违反规定。 调用基于扩展的实现具有副作用,而不将函数声明为具有副作用是未定义的行为,应避免这样做。允许使用 Varargs 内部函数。 若要声明 varargs 函数,请在声明中指定
Varargs="true"
。 虽然基于表达式的实现声明vararg
函数是合法的,但目前只有基于扩展的实现才能访问变量参数。 使用基于扩展的实现,Execute()
函数接收实际传入的所有参数,而不仅仅是声明的参数。不支持将类/结构/联合类型作为参数使用的内部函数。 返回类/结构/联合类型是可以的。 (对类/结构/联合类型的指针或引用可以作为参数类型)。
自定义对内部函数的调用的图标。
默认情况下,调用内部函数时,表达式会在与函数调用关联的 Watch 窗口中提供粉红色菱形图标。 可以通过使用以下值之一指定 Category
属性来替代此行为:
- 方法。 使用粉红色菱形图标,通常用于方法调用(默认值)。
- 财产。 使用通常与属性相关联的黑色扳手图标。
- 数据。 使用通常用于数据的蓝色菱形图标。
通过将内部函数与 <Item>
元素组合在一起,可以创作一个 NatVis 文件,其中项表达式具有扳机属性图标:
<Type Name="MyClass">
<Intrinsic Name="GetValue" ReturnType="int" Expression="m_value" Category="Property" />
<Expand>
<Item Name="Value">this->GetValue()</Item>
</Expand>
</Type>
备注
将图标选择置于函数级别,而不是 <Item>
级别,可避免在评估全名时图标自定义丢失的问题。 由于全名包括对函数的调用,因此它具有与 <Item>
本身相同的图标自定义。