Tworzenie niestandardowych widoków obiektów języka C++ w debugerze przy użyciu struktury Natvis
Struktura Natvis programu Visual Studio dostosowuje sposób wyświetlania typów natywnych w oknach zmiennych debugera, takich jak Locals i Watch windows, a w DataTips. Wizualizacje Natvis mogą pomóc w uwidocznieniu typów tworzonych podczas debugowania.
Natvis zastępuje plik autoexp.dat we wcześniejszych wersjach programu Visual Studio składnią XML, lepszą diagnostyką, przechowywaniem wersji i obsługą wielu plików.
Nota
Dostosowania natvis działają z klasami i strukturami, ale nie definicjami typów.
Wizualizacje Natvis
Za pomocą struktury Natvis można tworzyć reguły wizualizacji dla tworzonych typów, aby deweloperzy mogli je łatwiej zobaczyć podczas debugowania.
Na przykład poniższa ilustracja przedstawia zmienną typu Windows::UI::XAML::Controls::TextBox w oknie debugera bez zastosowanych wizualizacji niestandardowych.
Wyróżniony wiersz pokazuje właściwość Text
klasy TextBox
. Hierarchia klas złożonych utrudnia znalezienie tej właściwości. Debuger nie wie, jak interpretować typ ciągu niestandardowego, więc nie można zobaczyć ciągu przechowywanego w polu tekstowym.
Ten sam TextBox
wygląda znacznie prościej w oknie zmiennej, gdy stosowane są niestandardowe reguły wizualizatora Natvis. Ważni członkowie klasy są wyświetlani razem, a debugger pokazuje wartość ciągu bazowego niestandardowego typu znakowego.
Używanie plików natvis w projektach języka C++
Usługa Natvis używa plików .natvis do określania reguł wizualizacji. Plik .natvis jest plikiem XML z rozszerzeniem .natvis. Schemat Natvis jest zdefiniowany w folderze instalacji programu <VS>\Xml\Schemas\1033\natvis.xsd.
Podstawowa struktura pliku .natvis składa się z jednego lub więcej elementów Type
reprezentujących wpisy wizualizacji. W pełni kwalifikowana nazwa każdego elementu Type
jest określona w atrybucie 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>
Program Visual Studio udostępnia niektóre pliki .natvis w folderze instalacyjnym programu <VS>\Common7\Packages\Debugger\Visualizers. Te pliki mają reguły wizualizacji dla wielu typowych typów i mogą służyć jako przykłady pisania wizualizacji dla nowych typów.
Dodawanie pliku natvis do projektu C++
Do dowolnego projektu C++ można dodać plik .natvis.
Aby dodać nowy plik .natvis:
Wybierz węzeł projektu C++ w eksploratorze rozwiązań i wybierz pozycję Project>Dodaj nowy elementlub kliknij projekt prawym przyciskiem myszy i wybierz Dodaj>Nowy element.
Jeśli nie widzisz wszystkich szablonów elementów, wybierz pozycję Pokaż wszystkie szablony.
W oknie dialogowym Dodawanie nowego elementu wybierz pozycję Visual C++>Utility>Debugger visualization file (natvis).
Nadaj plikowi nazwę i wybierz pozycję Dodaj.
Nowy plik zostanie dodany do eksploratora rozwiązań i zostanie otwarty w okienku dokumentów programu Visual Studio.
Debuger programu Visual Studio automatycznie ładuje pliki .natvis w projektach języka C++, a domyślnie dołącza je również podczas budowania projektu do pliku .pdb. W przypadku debugowania utworzonej aplikacji debuger ładuje plik .natvis z pliku .pdb, nawet jeśli nie masz otwartego projektu. Jeśli nie chcesz, aby plik .natvis był uwzględniony w pliku .pdb, możesz wykluczyć go z wygenerowanego pliku .pdb.
Aby wykluczyć plik .natvis z .pdb:
Wybierz plik .natvis w eksploratorze rozwiązań , a następnie wybierz ikonę właściwości lub kliknij plik prawym przyciskiem myszy i wybierz Właściwości.
Kliknij strzałkę obok pozycji Wykluczone z Kompilacji i wybierz Tak, a następnie wybierz OK.
Notatka
W przypadku debugowania projektów wykonywalnych użyj elementów rozwiązania, aby dodać wszystkie pliki natvis, które nie znajdują się w .pdb, ponieważ nie ma dostępnego projektu języka C++.
Notatka
Reguły natvis ładowane z pliku .pdb mają zastosowanie wyłącznie do typów w modułach, do których odnosi się plik .pdb. Jeśli na przykład plik Module1.pdb ma wpis Natvis dla typu o nazwie Test
, dotyczy to tylko klasy Test
w Module1.dll. Jeśli inny moduł definiuje również klasę o nazwie Test
, wpis Module1.pdb Natvis nie ma do niego zastosowania.
Aby zainstalować i zarejestrować plik .natvis za pośrednictwem pakietu VSIX:
Pakiet VSIX może instalować i rejestrować pliki .natvis. Niezależnie od tego, gdzie są zainstalowane, wszystkie zarejestrowane pliki .natvis są automatycznie pobierane podczas debugowania.
Dołącz plik .natvis w pakiecie VSIX. Na przykład dla następującego pliku projektu:
<?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>
Zarejestruj plik .natvis w pliku source.extension.vsixmanifest:
<?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>
Lokalizacje plików Natvis
Możesz dodać pliki .natvis do katalogu użytkownika lub do katalogu systemowego, jeśli chcesz, aby były one stosowane do wielu projektów.
Pliki .natvis są oceniane w następującej kolejności:
Wszystkie pliki .natvis, które są osadzone w .pdb, z którego dokonujesz debugowania, chyba że plik o tej samej nazwie istnieje w załadowanym projekcie.
Wszystkie pliki .natvis, które znajdują się w załadowanym projekcie języka C++ lub rozwiązaniu najwyższego poziomu. Ta grupa obejmuje wszystkie załadowane projekty języka C++, w tym biblioteki klas, ale nie projekty w innych językach.
Wszystkie pliki .natvis zainstalowane i zarejestrowane za pośrednictwem pakietu VSIX.
- Katalog Natvis specyficzny dla użytkownika (na przykład %USERPROFILE%\Documents\Visual Studio 2022\Visualizers).
- Katalog Natvis specyficzny dla użytkownika (na przykład %USERPROFILE%\Documents\Visual Studio 2019\Visualizers).
- Katalog Natvis obejmujący cały system (<folder instalacyjny programu Microsoft Visual Studio>\Common7\Packages\Debugger\Visualizers). Ten katalog zawiera pliki .natvis zainstalowane w programie Visual Studio. Jeśli masz uprawnienia administratora, możesz dodać pliki do tego katalogu.
Modyfikowanie plików natvis podczas debugowania
Podczas debugowania projektu można zmodyfikować plik natvis w środowisku IDE. Otwórz plik w tym samym wystąpieniu programu Visual Studio, za pomocą którego debugujesz, zmodyfikuj go i zapisz. Gdy tylko plik zostanie zapisany, okna Watch i Locals zaktualizują się, aby odzwierciedlić zmianę.
Możesz również dodawać lub usuwać pliki natvis w rozwiązaniu, które debugujesz, a program Visual Studio dodaje lub usuwa odpowiednie wizualizacje.
Nie można zaktualizować plików .natvis osadzonych w plikach .pdb podczas debugowania.
Jeśli zmodyfikujesz plik .natvis poza programem Visual Studio, zmiany nie zostaną zastosowane automatycznie. Aby zaktualizować okna debugera, możesz ponownie wykonać polecenie .natvisreload w oknie Natychmiastowym. Następnie zmiany zostaną zastosowane bez ponownego uruchomienia sesji debugowania.
Użyj również polecenia .natvisreload, aby uaktualnić plik .natvis do nowszej wersji. Na przykład plik .natvis może zostać zaewidencjonowany w kontroli źródła i chcesz pobrać ostatnie zmiany wprowadzone przez inną osobę.
Wyrażenia i formatowanie
Wizualizacje Natvis używają wyrażeń języka C++, aby określić elementy danych do wyświetlenia. Oprócz ulepszeń i ograniczeń wyrażeń języka C++ w debugerze, które są opisane w Operator kontekstu (C++), należy pamiętać o następujących kwestiach:
Wyrażenia Natvis są obliczane w kontekście wizualizowanego obiektu, a nie bieżącej ramki stosu. Na przykład
x
w wyrażeniu Natvis odwołuje się do pola o nazwie x x w wizualizowanym obiekcie, a nie do zmiennej lokalnej o nazwie x w bieżącej funkcji. Nie można uzyskać dostępu do zmiennych lokalnych w wyrażeniach natvis, chociaż można uzyskać dostęp do zmiennych globalnych.Wyrażenia Natvis nie zezwalają na ocenę funkcji ani efekty uboczne. Wywołania funkcji i operatory przypisania są ignorowane. Ponieważ funkcje wewnętrzne debugera są wolne od skutków ubocznych, mogą być swobodnie wywoływane z dowolnego wyrażenia Natvis, mimo że inne wywołania funkcji są niedozwolone.
Aby kontrolować sposób wyświetlania wyrażenia, można użyć dowolnego specyfikatora formatu opisanego w Specyfikatory formatu w języku C++. Specyfikatory formatu są ignorowane, gdy wpis jest używany wewnętrznie przez usługę Natvis, na przykład wyrażenie
Size
w rozszerzeniu ArrayItems.
Notatka
Ponieważ dokument Natvis to XML, wyrażenia nie mogą bezpośrednio używać symboli & (ampersand), większy niż (>), mniejszy niż (<) ani operatorów przesunięcia. Te znaki należy wyeksportować zarówno w treści elementu, jak i w wyrażeniach warunkowych. Na przykład:
\<Item Name="HiByte"\>(byte)(_flags \>\> 24),x\</Item\>
\<Item Name="HiByteStatus" Condition="(_flags \& 0xFF000000) == 0"\>"None"\</Item\>
\<Item Name="HiByteStatus" Condition="(_flags \& 0xFF000000) != 0"\>"Some"\</Item\>
Widoki Natvis
Można zdefiniować różne widoki Natvis, aby wyświetlać typy na różne sposoby. Na przykład poniżej przedstawiono wizualizację std::vector
, która definiuje uproszczony widok o nazwie simple
. Elementy DisplayString
i ArrayItems
są wyświetlane w widoku domyślnym i widoku simple
, natomiast elementy [size]
i [capacity]
nie są wyświetlane w widoku simple
.
<Type Name="std::vector<*>">
<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>
W oknie Watch użyj specyfikatora formatu „view” aby określić alternatywny widok. Prosty widok jest wyświetlany jako vec,view(simple):
okno
Błędy natvis
Gdy debuger napotka błędy we wpisie wizualizacji, ignoruje je. Wyświetla typ w postaci pierwotnej lub wybiera inną odpowiednią wizualizację. Możesz użyć diagnostyki Natvis, aby zrozumieć, dlaczego debuger zignorował wpis wizualizacji oraz aby zobaczyć błędy składni i analizy parsującej.
Aby włączyć diagnostykę natvis:
- W obszarze Tools>Options (or Debug>Options) >Debugowanie okna danych wyjściowych>ustaw komunikaty diagnostyczne natvis () Tylko język C++błąd, ostrzeżenie lub pełne, a następnie wybierz pozycję OK.
Błędy są wyświetlane w oknie danych wyjściowych.
Odniesienie do składni Natvis
Następujące elementy i atrybuty mogą być używane w pliku Natvis.
AutoVisualizer: element
Element AutoVisualizer
jest węzłem głównym pliku .natvis i zawiera atrybut przestrzeni nazw xmlns:
.
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
.
.
</AutoVisualizer>
Element AutoVisualizer
może mieć Type, HResult, UIVisualizeri CustomVisualizer jako elementy podrzędne.
Element typu
Podstawowy przykład Type
wygląda następująco:
<Type Name="[fully qualified type name]">
<DisplayString Condition="[Boolean expression]">[Display value]</DisplayString>
<Expand>
...
</Expand>
</Type>
Element Type
określa:
Jakiego typu wizualizację należy użyć (atrybut
Name
).Jak powinna wyglądać wartość obiektu tego typu (element
DisplayString
).Jak powinni wyglądać członkowie typu, gdy użytkownik rozwinie typ w oknie zmiennej (węzeł
Expand
).
Klasy szablonowe
Atrybut Name
elementu Type
akceptuje gwiazdkę *
jako symbol wieloznaczny, który może być używany dla nazw klas szablonów.
W poniższym przykładzie ta sama wizualizacja jest używana, czy obiekt jest CAtlArray<int>
, czy CAtlArray<float>
. Jeśli istnieje określony wpis wizualizacji dla CAtlArray<float>
, ma pierwszeństwo przed ogólnym.
<Type Name="ATL::CAtlArray<*>">
<DisplayString>{{Count = {m_nSize}}}</DisplayString>
</Type>
Można odnosić się do parametrów szablonu w wpisie wizualizacji, używając makr $T1, $T2, itd. Aby znaleźć przykłady tych makr, zobacz pliki .natvis dostarczane z programem Visual Studio.
Dopasowywanie typów wizualizatora
Jeśli nie można zweryfikować wpisu wizualizacji, zostanie użyta następna dostępna wizualizacja.
Atrybut możliwy do dziedziczenia
Opcjonalny atrybut Inheritable
określa, czy wizualizacja ma zastosowanie tylko do typu podstawowego, czy do typu podstawowego i wszystkich typów pochodnych. Wartość domyślna Inheritable
to true
.
W poniższym przykładzie wizualizacja ma zastosowanie tylko do typu BaseClass
:
<Type Name="Namespace::BaseClass" Inheritable="false">
<DisplayString>{{Count = {m_nSize}}}</DisplayString>
</Type>
Atrybut priorytetu
Opcjonalny atrybut Priority
określa kolejność używania definicji alternatywnych, jeśli nie można przeanalizować definicji. Możliwe wartości Priority
to: Low
, MediumLow
,Medium
, MediumHigh
i High
. Wartość domyślna to Medium
. Atrybut Priority
rozróżnia tylko priorytety w tym samym pliku .natvis.
Poniższy przykład najpierw analizuje wpis, który odpowiada wersji STL z 2015 roku. Jeśli analiza składniowa się nie powiedzie, użyje alternatywnego wpisu dla biblioteki STL z roku 2013.
<!-- VC 2013 -->
<Type Name="std::reference_wrapper<*>" Priority="MediumLow">
<DisplayString>{_Callee}</DisplayString>
<Expand>
<ExpandedItem>_Callee</ExpandedItem>
</Expand>
</Type>
<!-- VC 2015 -->
<Type Name="std::reference_wrapper<*>">
<DisplayString>{*_Ptr}</DisplayString>
<Expand>
<Item Name="[ptr]">_Ptr</Item>
</Expand>
</Type>
Atrybut opcjonalny
Atrybut Optional
można umieścić w dowolnym węźle. Jeśli nie można przeanalizować podexpressionu wewnątrz opcjonalnego węzła, debuger ignoruje ten węzeł, ale stosuje pozostałe reguły Type
. W poniższym typie [State]
nie jest opcjonalna, ale [Exception]
jest opcjonalna. Jeśli MyNamespace::MyClass
ma pole o nazwie _M_exceptionHolder
, zostanie wyświetlone zarówno węzeł [State]
, jak i węzeł [Exception]
, ale jeśli nie ma pola _M_exceptionHolder
, zostanie wyświetlony tylko węzeł [State]
.
<Type Name="MyNamespace::MyClass">
<Expand>
<Item Name="[State]">_M_State</Item>
<Item Name="[Exception]" Optional="true">_M_exceptionHolder</Item>
</Expand>
</Type>
Atrybut warunku
Opcjonalny atrybut Condition
jest dostępny dla wielu elementów wizualizacji i określa, kiedy należy użyć reguły wizualizacji. Jeśli wyrażenie wewnątrz atrybutu warunku zostanie rozpoznane jako false
, reguła wizualizacji nie ma zastosowania. Jeśli wynik równa się true
lub nie ma atrybutu Condition
, zastosowano wizualizację. Tego atrybutu można użyć dla logiki if-else w wpisach wizualizacji.
Na przykład poniższa wizualizacja zawiera dwa elementy DisplayString
dla inteligentnego wskaźnika. Gdy element członkowski _Myptr
jest pusty, warunek pierwszego elementu DisplayString
jest rozpoznawany jako true
, tak aby formularz był wyświetlany. Gdy element członkowski _Myptr
nie jest pusty, warunek przyjmuje wartość false
, a drugi element DisplayString
zostaje wyświetlony.
<Type Name="std::auto_ptr<*>">
<DisplayString Condition="_Myptr == 0">empty</DisplayString>
<DisplayString>auto_ptr {*_Myptr}</DisplayString>
<Expand>
<ExpandedItem>_Myptr</ExpandedItem>
</Expand>
</Type>
Atrybuty IncludeView i ExcludeView
Atrybuty IncludeView
i ExcludeView
określają elementy do wyświetlania lub nie są wyświetlane w określonych widokach. Na przykład w poniższej specyfikacji Natvis std::vector
widok simple
nie wyświetla elementów [size]
i [capacity]
.
<Type Name="std::vector<*>">
<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>
Można użyć atrybutów IncludeView
i ExcludeView
na typach i poszczególnych członkach.
Element wersji
Element Version
określa wpis wizualizacji dla określonego modułu i wersji. Element Version
pomaga uniknąć kolizji nazw, zmniejsza nieumyślne niezgodności i umożliwia różne wizualizacje dla różnych wersji typów.
Jeśli wspólny plik nagłówkowy używany przez różne moduły definiuje typ, wizualizacja wersji jest wyświetlana tylko wtedy, gdy typ znajduje się w określonej wersji modułu.
W poniższym przykładzie wizualizacja ma zastosowanie tylko dla typu DirectUI::Border
znalezionego w Windows.UI.Xaml.dll
z wersji 1.0 do 1.5.
<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>
Nie potrzebujesz zarówno Min
, jak i Max
. Są to atrybuty opcjonalne. Nie są obsługiwane żadne symbole wieloznaczne.
Atrybut Name
jest w formacie nazwa pliku.ext, na przykład hello.exe lub some.dll. Nazwy ścieżek nie są dozwolone.
element DisplayString
Element DisplayString
określa ciąg, który ma być wyświetlany jako wartość zmiennej. Akceptuje dowolne ciągi znaków łączone z wyrażeniami. Wszystko wewnątrz nawiasów klamrowych jest interpretowane jako wyrażenie. Na przykład następujący wpis DisplayString
:
<Type Name="CPoint">
<DisplayString>{{x={x} y={y}}}</DisplayString>
</Type>
Oznacza, że zmienne typu CPoint
są wyświetlane jak na tej ilustracji.
W wyrażeniu DisplayString
x
i y
, które są członkami CPoint
, znajdują się wewnątrz nawiasów klamrowych, więc ich wartości są obliczane. W przykładzie pokazano również, jak można uniknąć nawiasu klamrowego przy użyciu podwójnych nawiasów klamrowych ( {{
lub }}
).
Notatka
Element DisplayString
jest jedynym, który akceptuje dowolne ciągi znaków oraz składnię nawiasów klamrowych. Wszystkie inne elementy wizualizacji akceptują tylko wyrażenia, które może ocenić debuger.
StringView element
Element StringView
definiuje wartość, którą debuger może wysłać do wbudowanego wizualizatora tekstu. Na przykład biorąc pod uwagę następującą wizualizację dla typu ATL::CStringT
:
<Type Name="ATL::CStringT<wchar_t,*>">
<DisplayString>{m_pszData,su}</DisplayString>
</Type>
Obiekt CStringT
jest wyświetlany w oknie zmiennych według następującego przykładu:
element
Dodanie elementu StringView
informuje debuger, że może wyświetlać wartość jako wizualizację tekstową.
<Type Name="ATL::CStringT<wchar_t,*>">
<DisplayString>{m_pszData,su}</DisplayString>
<StringView>m_pszData,su</StringView>
</Type>
Podczas debugowania można wybrać ikonę lupy obok zmiennej, a następnie wybrać Wizualizator tekstu, aby wyświetlić ciąg, na który wskazuje m_pszData.
Wyrażenie {m_pszData,su}
zawiera specyfikator formatu C++ su, aby wyświetlić wartość jako ciąg Unicode. Aby uzyskać więcej informacji, zobacz specyfikatory formatu w języku C++.
Rozwiń element
Opcjonalny węzeł Expand
dostosowuje elementy podrzędne typu wizualizowanego podczas rozwijania typu w oknie zmiennej. Węzeł Expand
akceptuje listę węzłów podrzędnych, które definiują elementy podrzędne.
Jeśli w wizualizacji nie określono węzła
Expand
, elementy podrzędne stosują domyślne reguły rozszerzenia.Jeśli węzeł
Expand
jest określony bez węzłów podrzędnych, typ nie jest rozszerzalny w oknach debugera.
Rozszerzenie elementu
Element Item
jest najbardziej podstawowym i typowym elementem w węźle Expand
.
Item
definiuje pojedynczy element podrzędny. Na przykład klasa CRect
z polami top
, left
, right
i bottom
ma następujący wpis wizualizacji:
<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>
W oknie debugera typ CRect
wygląda następująco:
Debuger oblicza wyrażenia określone w elementach Width
i Height
oraz wyświetla wartości w kolumnie Wartość okna zmiennej.
Debugger automatycznie tworzy węzeł [Widok surowy] dla każdego rozszerzenia niestandardowego. Na powyższym zrzucie ekranu przedstawiono węzeł [Widok nieprzetworzony] w rozwiniętej postaci, aby pokazać, jak domyślny widok nieprzetworzony obiektu różni się od jego wizualizacji Natvis. Rozszerzenie domyślne tworzy poddrzewo dla klasy bazowej i wyświetla listę wszystkich elementów członkowskich danych klasy bazowej jako elementów podrzędnych.
Notatka
Jeśli wyrażenie elementu odnosi się do typu złożonego, węzeł Item może być rozszerzany.
Rozszerzenie ArrayItems
Użyj węzła ArrayItems
, aby debuger programu Visual Studio interpretował typ jako tablicę i wyświetlał poszczególne elementy. Wizualizacja std::vector
jest dobrym przykładem:
<Type Name="std::vector<*>">
<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>
Po rozwinięciu w oknie zmiennych, std::vector
pokazuje swoje poszczególne elementy.
Węzeł ArrayItems
musi mieć następujące elementy:
- Wyrażenie
Size
(które musi zostać obliczone na liczbę całkowitą), aby debuger zrozumiał długość tablicy. - Wyrażenie
ValuePointer
wskazujące pierwszy element (który musi być wskaźnikiem typu elementu, który nie jestvoid*
).
Wartość domyślna dolnej granicy tablicy to 0. Aby zastąpić wartość, użyj elementu LowerBound
. Pliki .natvis dostarczane z programem Visual Studio zawierają przykłady.
Notatka
Możesz użyć operatora []
, na przykład vector[i]
, z dowolną jednowymiarową wizualizacją tablicy, która używa ArrayItems
, nawet jeśli sam typ (na przykład CATLArray
) nie zezwala na ten operator.
Można również określić tablice wielowymiarowe. W takim przypadku debuger potrzebuje nieco więcej informacji, aby prawidłowo wyświetlać elementy podrzędne:
<Type Name="Concurrency::array<*,*>">
<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>
<LowerBound>0</LowerBound>
</ArrayItems>
</Expand>
</Type>
-
Direction
określa, czy tablica jest w porządku wierszowym lub kolumnowym. -
Rank
określa rangę tablicy. - Element
Size
akceptuje niejawny parametr$i
, który zastępuje indeksem wymiaru, aby znaleźć długość tablicy w tym wymiarze.- W poprzednim przykładzie wyrażenie
_M_extent.M_base[0]
powinno podać długość zerowego wymiaru,_M_extent._M_base[1]
pierwszego, itd.
- W poprzednim przykładzie wyrażenie
-
LowerBound
określa dolną granicę każdego wymiaru tablicy. W przypadku tablic wielowymiarowych można określić wyrażenie, które używa niejawnego parametru$i
. Parametr$i
zostanie zastąpiony indeksem wymiaru, aby znaleźć dolną granicę tablicy w tym wymiarze.- W poprzednim przykładzie wszystkie wymiary zaczynają się od 0. Jeśli jednak miałbyś
($i == 1) ? 1000 : 100
jako dolną granicę, wymiar 0 rozpocznie się od 100, a pierwszy wymiar rozpocznie się od 1000.- , na przykład
[100, 1000], [100, 1001], [100, 1002], ... [101, 1000], [101, 1001],...
- , na przykład
- W poprzednim przykładzie wszystkie wymiary zaczynają się od 0. Jeśli jednak miałbyś
Oto jak dwuwymiarowy obiekt Concurrency::array
wygląda w oknie debugera:
Rozszerzenie IndexListItems
Można użyć rozszerzenia ArrayItems
tylko wtedy, gdy elementy tablicy są ułożone kolejno w pamięci. Debuger dostaje się do następnego elementu, po prostu zwiększając wskaźnik. Jeśli musisz manipulować indeksem prowadzącym do węzła wartości, użyj węzłów IndexListItems
. Oto wizualizacja z węzłem IndexListItems
:
<Type Name="Concurrency::multi_link_registry<*>">
<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>
Jedyną różnicą między ArrayItems
a IndexListItems
jest ValueNode
, która oczekuje pełnego wyrażenia do i-tego elementu z niejawnego parametru $i
.
Uwaga
Możesz użyć operatora []
, na przykład vector[i]
, z dowolną jednowymiarową wizualizacją tablicy, która używa IndexListItems
, nawet jeśli sam typ (na przykład CATLArray
) nie zezwala na ten operator.
Rozszerzenie LinkedListItems
Jeśli wizualizowany typ reprezentuje połączoną listę, debuger może wyświetlić jego elementy podrzędne przy użyciu węzła LinkedListItems
. Poniższa wizualizacja dla typu CAtlList
używa LinkedListItems
:
<Type Name="ATL::CAtlList<*,*>">
<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>
Element Size
odnosi się do długości listy.
HeadPointer
wskazuje pierwszy element, NextPointer
odwołuje się do następnego elementu, a ValueNode
odnosi się do wartości elementu.
Debuger ocenia wyrażenia NextPointer
i ValueNode
w kontekście elementu węzła LinkedListItems
, a nie nadrzędnego typu listy. W poprzednim przykładzie CAtlList
ma klasę CNode
(znaleziono w atlcoll.h
), która jest węzłem połączonej listy.
m_pNext
i m_element
to pola tej klasy CNode
, a nie klasy CAtlList
.
ValueNode
można pozostawić puste lub użyć this
, aby odwołać się do samego węzła LinkedListItems
.
Rozszerzenie CustomListItems
Rozszerzenie CustomListItems
umożliwia pisanie niestandardowej logiki przechodzenia przez strukturę danych, taką jak tabela skrótu. Użyj CustomListItems
, aby wizualizować struktury danych, które mogą używać wyrażeń języka C++ dla wszystkich elementów potrzebnych do oceny, ale nie pasują do formy ArrayItems
, IndexListItems
lub LinkedListItems
.
Za pomocą Exec
można wykonywać kod wewnątrz rozszerzenia CustomListItems
przy użyciu zmiennych i obiektów zdefiniowanych w rozszerzeniu. Operatory logiczne, operatory arytmetyczne i operatory przypisania można używać z Exec
. Nie można użyć Exec
do oceny funkcji, z wyjątkiem funkcji wewnętrznych debugera obsługiwanych przez ewaluatora wyrażeń języka C++.
Poniższa wizualizacja CAtlMap
jest doskonałym przykładem, gdzie CustomListItems
jest odpowiednie.
<Type Name="ATL::CAtlMap<*,*,*,*>">
<AlternativeType Name="ATL::CMapToInterface<*,*,*>"/>
<AlternativeType Name="ATL::CMapToAutoPtr<*,*,*>"/>
<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>
Rozszerzenie TreeItems
Jeśli wizualizowany typ reprezentuje drzewo, debuger może przejść przez drzewo i wyświetlić jego elementy podrzędne przy użyciu węzła TreeItems
. Oto wizualizacja typu std::map
przy użyciu węzła TreeItems
:
<Type Name="std::map<*>">
<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>
Składnia jest podobna do węzła LinkedListItems
.
LeftPointer
, RightPointer
i ValueNode
są oceniane w kontekście klasy węzła drzewa.
ValueNode
można pozostawić puste lub użyć this
, aby odwołać się do samego węzła TreeItems
.
Rozszerzenie funkcji ExpandedItem
Element ExpandedItem
generuje zagregowany widok podrzędny, wyświetlając właściwości klas bazowych lub składowych danych tak, jakby były dziećmi wizualizowanego typu. Debuger oblicza określone wyrażenie i dołącza węzły podrzędne wyniku do listy podrzędnej zwizualizowanego typu.
Na przykład inteligentny typ wskaźnika auto_ptr<vector<int>>
zazwyczaj jest wyświetlany jako:
Aby wyświetlić wartości wektora, należy zagłębić się dwa poziomy w oknie zmiennej, przechodząc przez człon _Myptr
. Dodając element ExpandedItem
, można wyeliminować zmienną _Myptr
z hierarchii i bezpośrednio wyświetlić elementy wektorów:
<Type Name="std::auto_ptr<*>">
<DisplayString>auto_ptr {*_Myptr}</DisplayString>
<Expand>
<ExpandedItem>_Myptr</ExpandedItem>
</Expand>
</Type>
W poniższym przykładzie pokazano, jak agregować właściwości z klasy bazowej w klasie pochodnej. Załóżmy, że klasa CPanel
pochodzi z CFrameworkElement
. Zamiast powtarzać właściwości pochodzące z klasy podstawowej CFrameworkElement
, wizualizacja węzła ExpandedItem
dołącza te właściwości do listy podrzędnej klasy CPanel
.
<Type Name="CPanel">
<DisplayString>{{Name = {*(m_pstrName)}}}</DisplayString>
<Expand>
<Item Name="IsItemsHost">(bool)m_bItemsHost</Item>
<ExpandedItem>*(CFrameworkElement*)this,nd</ExpandedItem>
</Expand>
</Type>
Specyfikator formatu , który wyłącza dopasowywanie wizualizacji dla klasy pochodnej, jest konieczne tutaj. W przeciwnym razie wyrażenie *(CFrameworkElement*)this
spowodowałoby ponowne zastosowanie wizualizacji CPanel
, ponieważ domyślne reguły dopasowywania typów wizualizacji uznają ją za najbardziej odpowiednią. Użyj specyfikatora formatu , aby poinstruować debugera o użyciu wizualizacji klasy bazowej lub domyślne rozszerzenie, jeśli klasa bazowa nie ma wizualizacji.
Rozszerzenie elementu syntetycznego
Chociaż element ExpandedItem
zapewnia bardziej pochlebny widok danych poprzez wyeliminowanie hierarchii, węzeł Synthetic
wykonuje odwrotnie. Umożliwia utworzenie sztucznego elementu podrzędnego, który nie jest wynikiem wyrażenia. Sztuczny element może mieć własne elementy podrzędne. W poniższym przykładzie wizualizacja typu Concurrency::array
używa węzła Synthetic
, aby wyświetlić użytkownikowi komunikat diagnostyczny:
<Type Name="Concurrency::array<*,*>">
<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>
Rozszerzanie wewnętrzne
Niestandardowa funkcja wewnętrzna, którą można wywołać z wyrażenia. Elementowi <Intrinsic>
musi towarzyszyć składnik debugera, który implementuje funkcję za pośrednictwem interfejsu IDkmIntrinsicFunctionEvaluator140. Aby uzyskać więcej informacji na temat implementacji niestandardowej funkcji wewnętrznej, zobacz Implementacja niestandardowej funkcji wewnętrznej NatVis.
<Type Name="std::vector<*>">
<Intrinsic Name="size" Expression="(size_t)(_Mypair._Myval2._Mylast - _Mypair._Myval2._Myfirst)" />
<Intrinsic Name="capacity" Expression="(size_t)(_Mypair._Myval2._Myend - _Mypair._Myval2._Myfirst)" />
<DisplayString>{{ size={size()} }}</DisplayString>
<Expand>
<Item Name="[capacity]" ExcludeView="simple">capacity()</Item>
<Item Name="[allocator]" ExcludeView="simple">_Mypair</Item>
<ArrayItems>
<Size>size()</Size>
<ValuePointer>_Mypair._Myval2._Myfirst</ValuePointer>
</ArrayItems>
</Expand>
</Type>
HResult element
Element HResult
umożliwia dostosowanie informacji wyświetlanych dla HRESULT w oknach debuggera. Element HRValue
musi zawierać wartość 32-bitową HRESULT, która ma zostać dostosowana. Element HRDescription
zawiera informacje, które mają być wyświetlane w oknie debugera.
<HResult Name="MY_E_COLLECTION_NOELEMENTS">
<HRValue>0xABC0123</HRValue>
<HRDescription>No elements in the collection.</HRDescription>
</HResult>
Element UIVisualizer
Element UIVisualizer
rejestruje wtyczkę wizualizatora graficznego w debugerze. Wizualizator graficzny tworzy okno dialogowe lub inny interfejs, który pokazuje zmienną lub obiekt w sposób spójny z typem danych. Wtyczka wizualizatora musi być utworzona jako VSPackagei musi udostępnić usługę, którą może używać debuger. Plik .natvis zawiera informacje rejestracyjne dla wtyczki, takie jak jego nazwa, unikatowy identyfikator globalny (GUID) uwidocznionej usługi oraz typy, które może wizualizować.
Oto przykład elementu wizualizatora interfejsu użytkownika:
<?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>
Para atrybutów
ServiceId
-Id
identyfikujeUIVisualizer
.ServiceId
to identyfikator GUID usługi, którą udostępnia pakiet wizualizatora.Id
jest unikatowym identyfikatorem, który rozróżnia wizualizatory, jeśli usługa udostępnia więcej niż jeden. W poprzednim przykładzie ta sama usługa wizualizatora udostępnia dwa wizualizatory.Atrybut
MenuName
definiuje nazwę wizualizatora do wyświetlenia na liście rozwijanej obok ikony lupy w debugerze. Na przykład:menu skrótów Visualizera interfejsu użytkownika
Każdy typ zdefiniowany w pliku .natvis musi jawnie wymienić wszystkie wizualizatory interfejsu użytkownika, które mogą go wyświetlić. Debugger dopasowuje odwołania wizualizatora we wpisach typu do zarejestrowanych wizualizatorów. Na przykład następujący wpis typu dla std::vector
odwołuje się do UIVisualizer
w poprzednim przykładzie.
<Type Name="std::vector<int,*>">
<UIVisualizer ServiceId="{5452AFEA-3DF6-46BB-9177-C0B08F318025}" Id="1" />
</Type>
Przykład UIVisualizer
można zobaczyć w rozszerzeniu Image Watch używanym do wyświetlania map bitowych w pamięci.
element CustomVisualizer
CustomVisualizer
to punkt rozszerzalności określający rozszerzenie VSIX, które zapisujesz w celu sterowania wizualizacjami w programie Visual Studio Code. Aby uzyskać więcej informacji na temat pisania rozszerzeń VSIX, zobacz zestaw SDK Visual Studio.
Pisanie niestandardowego wizualizatora jest o wiele bardziej pracochłonne niż tworzenie definicji XML Natvis, ale nie ma ograniczeń dotyczących tego, co Natvis obsługuje lub nie obsługuje. Niestandardowe wizualizatory mają dostęp do pełnego zestawu interfejsów API rozszerzalności debugera, które mogą wykonywać zapytania i modyfikować proces debugowania lub komunikować się z innymi częściami programu Visual Studio.
Można użyć atrybutów Condition
, IncludeView
i ExcludeView
dla elementów CustomVisualizer
.
Ograniczenia
Dostosowania natvis działają z klasami i strukturami, ale nie definicjami typów.
Funkcja Natvis nie obsługuje wizualizatorów typów pierwotnych (na przykład int
, bool
) ani wskaźników do typów pierwotnych. W tym scenariuszu jedną z opcji jest użycie specyfikatora formatu odpowiedniego dla danego przypadku użycia. Jeśli na przykład używasz double* mydoublearray
w kodzie, możesz użyć specyfikatora formatu tablicy w oknie watch debugera, takim jak wyrażenie mydoublearray, [100]
, które pokazuje pierwsze 100 elementów.