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()
評価されるたびに、func-eval を実行するのではなく、実際に vector._Mypair._Myval2._Mylast - vector._Mypair._Myval2._Myfirst
を評価します。
同様に、LOWORD(0x12345678)
は func-eval なしでも、0x5678
を返します。
別の例については、組み込み拡張を参照してください。
組み込み関数を使用するためのガイドライン
組み込み関数を使用する場合は、次のガイドラインに注意してください。
組み込み関数は、PDB で定義された関数または相互にオーバーロードできます。
組み込み関数が、同じ名前と引数リストを持つ PDB 定義関数と競合すると、組み込み関数が優先されます。 同等の組み込み関数が存在する場合、PDB 関数を評価することはできません。
組み込み関数のアドレスを取得することはできません。呼び出しのみが可能です。
組み込みメンバー関数は、式で評価するために、それぞれの型の既定のビューに属している必要があります。 たとえば、
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
属性を指定することで、この動作をオーバーライドできます。 関数が副作用を持つものとしてマークされている場合、実装式の副作用が許可されます。 ただし、副作用が禁止されているコンテキストでの関数の呼び出しは許可されなくなりました。2 つの組み込み関数の定義が互いに競合する場合 (同じ名前、同じシグネチャ)、最後の関数が優先されます (同じファイル内)。
異なるファイル内では、優先度の高いファイル内のインスタンスが優先されます (プロジェクトはユーザー ディレクトリよりも高く、インストール ディレクトリよりも高くなります)。 優先順位の高い定義に解析されない式が含まれている場合、その定義は無視され、代わりに次に高い優先順位の定義が使用されます。
組み込み関数を実装するためのガイドライン
組み込み関数は、次の 2 つの可能な形式の実装をサポートします。
式に基づくもの
NatVis ファイルは、関数の戻り値に評価される式を定義します。 この式では、
<Parameter>
要素として宣言された、渡された任意の引数を使用できます。 クラス内で定義された関数も "インスタンス" 関数と見なされ、"this" ポインターにもアクセスできます。拡張機能に基づくもの
NatVis ファイルには、関数を実際に評価するデバッガー拡張機能を呼び出すための手順が用意されています。 デバッガー拡張機能には、コンコード 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
に単一要素配列を割り当てることで結果を返す必要があります。
関数を実装するには、次のガイドラインを使用します。
2 つの実装フォームを "ミックスして一致させる" のは違法です。 つまり、式とソース ID の両方を含めることはできません。
式ベースの実装の戻り値の型の指定は許可されますが、必須ではありません。 戻り値の型が指定されている場合、式の戻り値の型は完全に一致する必要があります (暗黙的なキャストは許可されません)。 戻り値の型が指定されていない場合、戻り値の型は式から推論されます。 拡張機能に基づく実装では、戻り値の型を NatVis ファイルで明記する必要があります。
他の組み込み関数への非再帰呼び出しが許可されます。 再帰は許可されません。
関数に副作用がある場合は、宣言で
SideEffect="true"
を指定する必要があります。 関数が副作用を持つよう宣言せずに、式ベースの実装が式に副作用を与えるのは無効です。 副作用を持つものとして関数を宣言せずに副作用を持つ拡張機能ベースの実装を呼び出すと、未定義の動作になり、回避する必要があります。Varargs 組み込み関数を使用できます。 varargs 関数を宣言するには、宣言で
Varargs="true"
を指定します。 式ベースの実装ではvararg
関数を宣言することは有効ですが、現時点では、可変引数にアクセスする方法を持つのは拡張機能ベースの実装のみです。 拡張ベースの実装では、Execute()
関数は、宣言された引数だけでなく、実際に渡されるすべての引数を受け取ります。クラス/構造体/共用体型を引数型として使用する組み込み関数はサポートされていません。 クラス/構造体/共用体の型を返しても問題ありません。 (クラス/構造体/共用体型へのポインターまたは参照は、引数型として OK です)。
組み込み関数の呼び出しのアイコンをカスタマイズします。
既定では、組み込み関数を呼び出すと、関数呼び出しに関連付けられた ウォッチ ウィンドウで、式にピンクのダイヤモンドのアイコンが表示されます。 この動作をオーバーライドするには、次のいずれかの値を使用して 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>
自体と同じアイコンのカスタマイズが行われます。