Implementieren einer benutzerdefinierten NatVis-intrinsischen Funktion für C++
In diesem Artikel erfahren Sie mehr über die Richtlinien für die Implementierung einer benutzerdefinierten systeminternen Funktion in einer NatVis-Visualisierung. Weitere Informationen zu NatVis finden Sie unter Erstellen benutzerdefinierter Ansichten von C++-Objekten.
Syntax
Eine NatVis-Datei kann eine systeminterne Funktion mithilfe der folgenden Syntax definieren:
<Intrinsic Name="Func" Expression="arg + 1">
<Parameter Name="arg" Type="int" />
</Intrinsic>
Sobald die Definition vorhanden ist, kann jeder Debuggerausdruck die Funktion wie jede andere Funktion aufrufen. Mit der vorherigen NatVis-Definition wird beispielsweise der Ausdruck Func(3)
als 4 ausgewertet.
Ein <Intrinsic>
-Element kann entweder auf Dateiebene oder innerhalb eines <Type>
-Elements vor anderen Elementen angezeigt werden. Intrinsische Funktionen, die innerhalb eines <Type>
definiert sind, definieren Mitgliedsfunktionen dieses Typs, während intrinsische Funktionen außerhalb eines <Type>
globale Funktionen definieren. Zum Beispiel:
<?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>
Im vorherigen Beispiel werden size()
und capacity()
als Memberfunktionen der std::vector
-Klasse definiert. Wenn ein Ausdruck also vector.size()
auswertet, wird tatsächlich vector._Mypair._Myval2._Mylast - vector._Mypair._Myval2._Myfirst
ausgewertet, anstatt eine funcEval durchzuführen.
Ebenso gibt LOWORD(0x12345678)
0x5678
zurück, auch ohne Func-Eval.
Ein weiteres Beispiel finden Sie unter Intrinsische Erweiterung.
Richtlinien für die Verwendung einer systeminternen Funktion
Beachten Sie bei der Verwendung einer systeminternen Funktion die folgenden Richtlinien:
Systeminterne Funktionen können entweder mit PDB-definierten Funktionen oder miteinander überladen werden.
Wenn eine systeminterne Funktion mit einer PDB-definierten Funktion mit der gleichen Namens- und Argumentliste in Konflikt tritt, gewinnt die systeminterne Funktion. Sie können die PDB-Funktion nicht auswerten, wenn eine entsprechende systeminterne Funktion vorhanden ist.
Sie können die Adresse einer systeminternen Funktion nicht übernehmen. Sie können es nur aufrufen.
Intrinsische Memberfunktionen müssen zur Standardansicht des jeweiligen Typs gehören, damit sie in einem Ausdruck ausgewertet werden können. Ein Typeintrag mit einer
IncludeView
Einschränkung kann z. B. keine systeminterne Funktion angeben.Systeminterne Funktionen können von jedem Ausdruck aufgerufen werden, einschließlich NatVis-Ausdrücken. Systeminterne Funktionen können sich auch gegenseitig aufrufen. Systeminterne Funktionen, die Rekursion verwenden, werden derzeit jedoch nicht unterstützt. Zum Beispiel:
<!-- 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>
Systeminterne Funktionen werden standardmäßig als nebeneffektfrei angenommen. Das heißt, sie können in Kontexten aufgerufen werden, die Nebenwirkungen nicht zulassen, und der Implementierungsausdruck darf keine Nebenwirkungen enthalten.
Die Definition kann dieses Verhalten überschreiben, indem das attribut
SideEffect
in der Deklaration angegeben wird. Wenn eine Funktion als mit Nebenwirkungen gekennzeichnet ist, sind in der Implementierung Nebenwirkungen zulässig. Das Aufrufen der Funktion in Kontexten, in denen Nebenwirkungen verboten sind, ist jedoch nicht mehr zulässig.Wenn die Definitionen von zwei systeminternen Funktionen miteinander in Konflikt stehen (derselbe Name, dieselbe Signatur), gewinnt der letzte (innerhalb derselben Datei).
Innerhalb verschiedener Dateien gewinnt die Instanz in der Datei mit höherer Priorität (Projekt ist höher als das Benutzerverzeichnis, das höher als das Installationsverzeichnis ist). Wenn eine Definition mit höherer Priorität einen Ausdruck enthält, der nicht analysiert wird, wird diese Definition ignoriert, und stattdessen wird die Definition mit der nächsten höchsten Priorität verwendet.
Richtlinien zum Implementieren einer systeminternen Funktion
Systeminterne Funktionen unterstützen zwei mögliche Implementierungsformen:
Ausdrucksbasiert
Die NatVis-Datei definiert einen Ausdruck, der den Rückgabewert der Funktion auswertet. Der Ausdruck kann alle übergebenen Argumente verwenden, die als
<Parameter>
-Elemente deklariert sind. In einer Klasse definierte Funktionen werden auch als "Instanz"-Funktionen angenommen und können auch auf den Zeiger "dieses" zugreifen.Erweiterungsbasiert
Die NatVis-Datei enthält Anweisungen zum Aufrufen einer Debuggererweiterung, um die Funktion tatsächlich auszuwerten. Eine Debuggererweiterung hat Vollzugriff auf die Concord-API und hat die Möglichkeit, Aufgaben auszuführen, die in einem NatVis-Ausdruck nicht möglich sind.
Um eine erweiterungsbasierte Implementierung bereitzustellen, sollte das <Intrinsic>
-Element das attribut Expression
weglassen und stattdessen SourceId
, LanguageId
, Id
und ReturnType
Attribute bereitstellen, wie im folgenden Beispiel angegeben:
<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>
Um die Funktion zu implementieren, muss die Debuggererweiterung die IDkmIntrinsicFunctionEvaluator140
Schnittstelle implementieren, wobei LanguageId
und SourceId
Filter verwendet werden, die den entsprechenden Werten des <Intrinsic>
Elements in der NatVis-Datei entsprechen. Wenn die Funktion aufgerufen wird, wird der Aufruf in die Execute()
-Methode der Komponente übersetzt:
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
);
Die Komponente empfängt die Bytes jedes Arguments über das argument Arguments
. Wenn es sich bei der fraglichen Funktion um eine Memberfunktion handelt, kommt der this
Zeiger zuerst, gefolgt von den expliziten Argumenten. Die Komponente sollte das Ergebnis zurückgeben, indem in pResults
ein Array mit einem einzigen Element zugewiesen wird, das die Bytes des Rückgabewerts speichert.
Verwenden Sie die folgenden Richtlinien zum Implementieren von Funktionen:
Es ist unzulässig, die beiden Implementierungsformulare zu "mischen und abzugleichen". Das heißt, Sie können weder einen Ausdruck noch eine Quell-ID gleichzeitig einschließen.
Das Angeben eines Rückgabetyps für eine ausdrucksbasierte Implementierung ist zulässig, aber nicht erforderlich. Wenn ein Rückgabetyp angegeben ist, muss der Rückgabetyp des Ausdrucks exakt mit diesem übereinstimmen (keine implizite Umwandlung zulässig). Wenn kein Rückgabetyp angegeben ist, wird der Rückgabetyp vom Ausdruck abgeleitet. Jede erweiterungsbasierte Implementierung muss einen Rückgabetyp in der NatVis-Datei angeben.
Nicht rekursive Aufrufe anderer systeminterner Funktionen sind zulässig. Die Rekursion ist nicht zulässig.
Wenn die Funktion Nebenwirkungen hat, müssen Sie
SideEffect="true"
in der Deklaration angeben. Es ist unzulässig, dass eine ausdrucksbasierte Implementierung Nebenwirkungen im Ausdruck hat, ohne die Funktion als nebenwirkend zu deklarieren. Das Aufrufen einer erweiterungsbasierten Implementierung, die Nebenwirkungen hat, ohne die Funktion als solche zu deklarieren, führt zu undefiniertem Verhalten und sollte vermieden werden.Systeminterne Varargs-Funktionen sind zulässig. Um eine Varargs-Funktion zu deklarieren, geben Sie
Varargs="true"
in der Deklaration an. Obwohl es für eine ausdrucksbasierte Implementierung zulässig ist, einevararg
-Funktion zu deklarieren, haben derzeit nur erweiterungsbasierte Implementierungen eine Möglichkeit, auf die Variablenargumente zuzugreifen. Bei einer erweiterungsbasierten Implementierung empfängt dieExecute()
-Funktion alle Argumente, die tatsächlich übergeben werden, nicht nur die deklarierten Argumente.Systeminterne Funktionen, die einen Klassen-/Struktur-/Union-Typ als Argumenttyp verwenden, werden nicht unterstützt. Das Zurückgeben eines Klassen-/Struktur-/Union-Typs ist OK. (Ein Zeiger oder Ein Verweis auf einen Klassen-/Struktur-/Union-Typ ist OK als Argumenttyp).
Passen Sie das Symbol für Aufrufe von systeminternen Funktionen an.
Wenn Sie eine systeminterne Funktion aufrufen, erhält der Ausdruck standardmäßig das rosa Rautensymbol im Watch Fenster, das Funktionsaufrufen zugeordnet ist. Sie können dieses Verhalten außer Kraft setzen, indem Sie das attribut Category
mit einem der folgenden Werte angeben:
- Methode. Verwenden Sie das rosa Rautensymbol, das in der Regel mit Methodenaufrufen (Standard) verwendet wird.
- Eigentum. Verwenden Sie das schwarze Schraubendrehersymbol, das in der Regel für Eigenschaften verwendet wird.
- Daten: Verwenden Sie das blaue Rautensymbol, das in der Regel mit Daten verwendet wird.
Durch die Kombination systeminterner Funktionen mit dem <Item>
-Element ist es möglich, eine NatVis-Datei zu erstellen, in der Elementausdrücke das Symbol für die Wrench-Eigenschaft aufweisen:
<Type Name="MyClass">
<Intrinsic Name="GetValue" ReturnType="int" Expression="m_value" Category="Property" />
<Expand>
<Item Name="Value">this->GetValue()</Item>
</Expand>
</Type>
Anmerkung
Wenn Sie die Symbolauswahl auf Funktionsebene anstelle der <Item>
Ebene platzieren, werden Probleme vermieden, bei denen die Symbolanpassung verloren geht, wenn der vollständige Name ausgewertet wird. Da der vollständige Name einen Aufruf der Funktion enthält, weist er die gleiche Symbolanpassung wie die <Item>
selbst auf.