Implementowanie niestandardowej funkcji wewnętrznej natVis dla języka C++
W tym artykule przedstawiono wskazówki dotyczące implementowania niestandardowej funkcji wewnętrznej w wizualizacji NatVis. Aby uzyskać więcej informacji na temat NatVis, zobacz Create custom views of C++ objects.
Składnia
Plik NatVis może definiować funkcję wewnętrzną przy użyciu następującej składni:
<Intrinsic Name="Func" Expression="arg + 1">
<Parameter Name="arg" Type="int" />
</Intrinsic>
Po utworzeniu definicji dowolne wyrażenie debugera może wywołać funkcję, podobnie jak każda inna funkcja. Na przykład przy użyciu powyższej definicji natVis wyrażenie Func(3)
daje wartość 4.
Element <Intrinsic>
może pojawić się na poziomie pliku lub wewnątrz elementu <Type>
przed innymi elementami. Funkcje wewnętrzne zdefiniowane w <Type>
definiują funkcje składowe tego typu, podczas gdy funkcje wewnętrzne zdefiniowane poza <Type>
definiują funkcje globalne. Na przykład:
<?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>
W poprzednim przykładzie size()
i capacity()
są definiowane jako funkcje składowe klasy std::vector
, więc za każdym razem, gdy wyrażenie oblicza vector.size()
, faktycznie oceni vector._Mypair._Myval2._Mylast - vector._Mypair._Myval2._Myfirst
, a nie wykona func-eval.
Podobnie jak LOWORD(0x12345678)
, 0x5678
jest zwracany również bez użycia func-eval.
Aby zapoznać się z innym przykładem, zobacz rozszerzenie wewnętrzne .
Wskazówki dotyczące używania funkcji wewnętrznej
Podczas korzystania z funkcji wewnętrznej należy pamiętać o następujących wytycznych:
Funkcje wewnętrzne mogą być przeciążone za pomocą funkcji zdefiniowanych przez plik PDB lub między sobą.
Gdy funkcja wewnętrzna powoduje konflikt z funkcją zdefiniowaną przez plik PDB o tej samej nazwie i liście argumentów, funkcja wewnętrzna wygra. Nie można ocenić funkcji PDB, jeśli istnieje równoważna funkcja wbudowana.
Nie można uzyskać adresu funkcji wewnętrznej; można ją tylko wywołać.
Funkcje wewnętrznego elementu członkowskiego muszą należeć do domyślnego widoku odpowiedniego typu, aby można je było ocenić w wyrażeniu. Na przykład wpis typu z ograniczeniem
IncludeView
może nie określać funkcji wewnętrznej.Funkcje wewnętrzne mogą być wywoływane z dowolnego wyrażenia, w tym z wyrażeń NatVis. Funkcje wewnętrzne mogą również wywoływać siebie nawzajem. Jednak funkcje wewnętrzne korzystające z rekursji nie są obecnie obsługiwane. Na przykład:
<!-- 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>
Domyślnie przyjmuje się, że funkcje wewnętrzne są wolne od skutków ubocznych. Oznacza to, że mogą być wywoływane w kontekstach, które nie zezwalają na skutki uboczne, a wyrażenie implementacji nie może zawierać efektów ubocznych.
Definicja może zastąpić to zachowanie, określając atrybut
SideEffect
w deklaracji. Jeśli funkcja jest oznaczona jako mająca skutki uboczne, skutki uboczne w wyrażeniu implementacji stają się dozwolone. Jednak wywoływanie funkcji w kontekstach, w których skutki uboczne są zabronione, nie jest już dozwolone.Gdy definicje dwóch funkcji wewnętrznych powodują konflikt ze sobą (ta sama nazwa, ten sam podpis), ostatnia wygrywa (w tym samym pliku).
Pomiędzy różnymi plikami, instancja w pliku o wyższym priorytecie wygrywa (projekt ma wyższy priorytet niż katalog użytkownika, który ma wyższy priorytet niż katalog instalacyjny). Jeśli definicja o wyższym priorytecie zawiera wyrażenie, którego nie można przetworzyć, ta definicja jest ignorowana, a zamiast tego jest używana definicja następnej najwyższej definicji priorytetu.
Wskazówki dotyczące implementowania funkcji wewnętrznej
Funkcje wewnętrzne obsługują dwie możliwe formy implementacji:
Oparte na wyrażeniach
Plik NatVis definiuje wyrażenie, które oblicza wartość zwracaną funkcji. Wyrażenie może używać dowolnych argumentów przekazanych do niego, które są zadeklarowane jako elementy
<Parameter>
. Przyjmuje się również, że funkcje zdefiniowane w klasie są funkcjami instancji i mogą uzyskiwać dostęp do wskaźnika „this”.Oparte na rozszerzeniach
Plik NatVis zawiera instrukcje dotyczące wywoływania rozszerzenia debugera w celu rzeczywistej oceny funkcji. Rozszerzenie debugera ma pełny dostęp do interfejsu API Concord i ma możliwość wykonywania zadań, które nie są możliwe w wyrażeniu NatVis.
Aby zapewnić implementację opartą na rozszerzeniach, element <Intrinsic>
powinien pominąć atrybut Expression
, a zamiast tego podać atrybuty SourceId
, LanguageId
, Id
i ReturnType
, jak podano w poniższym przykładzie:
<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>
Aby zaimplementować funkcję, rozszerzenie debugera musi zaimplementować interfejs IDkmIntrinsicFunctionEvaluator140
przy użyciu LanguageId
i SourceId
filtrów, które pasują do odpowiednich wartości elementu <Intrinsic>
w pliku NatVis. Po wywołaniu funkcji wywołanie przekłada się na metodę Execute()
składnika:
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
);
Składnik odbiera bajty każdego argumentu za pośrednictwem argumentu Arguments
. Jeśli dana funkcja jest funkcją składową, najpierw pojawia się wskaźnik this
, a następnie jawne argumenty. Składnik powinien zwrócić wynik, przydzielając tablicę z jednym elementem w pResults
, przechowując bajty zwracanej wartości.
Aby zaimplementować funkcje, skorzystaj z poniższych wskazówek:
Nie wolno łączyć i dopasowywać dwóch form implementacji. Oznacza to, że nie można dołączyć zarówno wyrażenia, jak i identyfikatora źródłowego.
Określanie typu zwracanego dla implementacji opartej na wyrażeniach jest dozwolone, ale nie jest wymagane. Jeśli określono typ zwracany, zwracany typ wyrażenia musi być dokładnie zgodny (nie jest dozwolone rzutowanie niejawne). Jeśli typ zwracany nie jest określony, zwracany typ jest wnioskowany z wyrażenia. Każda implementacja oparta na rozszerzeniach musi określać typ zwracany w pliku NatVis.
Niecykliczne wywołania do innych funkcji wewnętrznych są dozwolone. Rekursja nie jest dozwolona.
Jeśli funkcja ma skutki uboczne, należy określić
SideEffect="true"
w deklaracji. Implementacja oparta na wyrażeniach jest nielegalna, jeśli wyrażenie ma efekty uboczne, a funkcja nie deklaruje, że ma skutki uboczne. Wywoływanie implementacji opartej na rozszerzeniu, aby uzyskać skutki uboczne bez deklarowania funkcji jako mającej skutki uboczne, jest niezdefiniowanym zachowaniem i należy tego unikać.Funkcje wewnętrzne Varargs są dozwolone. Aby zadeklarować funkcję varargs, określ
Varargs="true"
w deklaracji. Chociaż implementacja oparta na wyrażeniach jest legalna do deklarowania funkcjivararg
, obecnie tylko implementacje oparte na rozszerzeniach mają sposób na dostęp do argumentów zmiennych. W przypadku implementacji opartej na rozszerzeniu funkcjaExecute()
odbiera wszystkie argumenty, które są rzeczywiście przekazywane, a nie tylko zadeklarowane argumenty.Funkcje wewnętrzne zużywające typ klasy/struktury/unii jako typ argumentu nie są obsługiwane. Zwracanie typu klasy/struktury/unii jest ok. (Wskaźnik lub odwołanie do klasy/struktury/typu unii jest OK jako typ argumentu).
Dostosuj ikonę wywołań funkcji wewnętrznych.
Domyślnie podczas wywoływania funkcji wewnętrznej wyrażenie otrzymuje różową ikonę rombu w oknie Watch skojarzonym z wywołaniami funkcji. To zachowanie można zastąpić, określając atrybut Category
przy użyciu jednej z następujących wartości:
- Metoda. Użyj różowej ikony diamentu, zwykle używanej z wywołaniami metody (ustawienie domyślne).
- Własność. Użyj czarnej ikony klucza, która jest zwykle używana z właściwościami.
- Dane. Użyj niebieskiej ikony diamentu, zwykle używanej z danymi.
Łącząc funkcje wbudowane z elementem <Item>
, można utworzyć plik NatVis, w którym wyrażenia elementów mają ikonę właściwości 'klucz':
<Type Name="MyClass">
<Intrinsic Name="GetValue" ReturnType="int" Expression="m_value" Category="Property" />
<Expand>
<Item Name="Value">this->GetValue()</Item>
</Expand>
</Type>
Nota
Umieszczenie wyboru ikony na poziomie funkcji, a nie na poziomie <Item>
, pozwala uniknąć problemów, w których dostosowanie ikony zostanie utracone przy ocenie pełnej nazwy. Ponieważ pełna nazwa zawiera wywołanie funkcji, ma taki sam wygląd ikony jak <Item>
.