Udostępnij za pośrednictwem


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.

domyślna wizualizacja TextBox

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.

dane TextBox przy użyciu wizualizatora

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:

  1. 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.

  2. W oknie dialogowym Dodawanie nowego elementu wybierz pozycję Visual C++>Utility>Debugger visualization file (natvis).

  3. 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:

  1. 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.

  2. 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.

  1. 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>
    
  2. 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:

  1. 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.

  2. 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.

  3. Wszystkie pliki .natvis zainstalowane i zarejestrowane za pośrednictwem pakietu VSIX.

  1. Katalog Natvis specyficzny dla użytkownika (na przykład %USERPROFILE%\Documents\Visual Studio 2022\Visualizers).
  1. Katalog Natvis specyficzny dla użytkownika (na przykład %USERPROFILE%\Documents\Visual Studio 2019\Visualizers).
  1. 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 \&gt;\&gt; 24),x\</Item\>
\<Item Name="HiByteStatus" Condition="(_flags \&amp; 0xFF000000) == 0"\>"None"\</Item\>
\<Item Name="HiByteStatus" Condition="(_flags \&amp; 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&lt;*&gt;">
    <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 Watch z prostym widokiem

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:

  1. Jakiego typu wizualizację należy użyć (atrybut Name).

  2. Jak powinna wyglądać wartość obiektu tego typu (element DisplayString).

  3. 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&lt;*&gt;">
    <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, MediumHighi 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&lt;*&gt;" Priority="MediumLow">
     <DisplayString>{_Callee}</DisplayString>
    <Expand>
        <ExpandedItem>_Callee</ExpandedItem>
    </Expand>
</Type>

<!-- VC 2015 -->
<Type Name="std::reference_wrapper&lt;*&gt;">
    <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ę truelub 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&lt;*&gt;">
  <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::vectorwidok simple nie wyświetla elementów [size] i [capacity].

<Type Name="std::vector&lt;*&gt;">
    <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.

Użyj elementu DisplayString

W wyrażeniu DisplayStringx 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&lt;wchar_t,*&gt;">
  <DisplayString>{m_pszData,su}</DisplayString>
</Type>

Obiekt CStringT jest wyświetlany w oknie zmiennych według następującego przykładu:

element CStringT DisplayString element

Dodanie elementu StringView informuje debuger, że może wyświetlać wartość jako wizualizację tekstową.

<Type Name="ATL::CStringT&lt;wchar_t,*&gt;">
  <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.

dane CStringT z wizualizatorem StringView

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, righti 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:

CRect z rozszerzeniem elementu item

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&lt;*&gt;">
  <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.

wektor std:: używający rozszerzenia ArrayItems

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 jest void*).

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&lt;*,*&gt;">
  <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.
  • 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],...

Oto jak dwuwymiarowy obiekt Concurrency::array wygląda w oknie debugera:

tablica dwuwymiarowa z rozszerzeniem ArrayItems

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&lt;*&gt;">
  <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&lt;*,*&gt;">
  <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, IndexListItemslub 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&lt;*,*,*,*&gt;">
    <AlternativeType Name="ATL::CMapToInterface&lt;*,*,*&gt;"/>
    <AlternativeType Name="ATL::CMapToAutoPtr&lt;*,*,*&gt;"/>
    <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&lt;*&gt;">
  <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, RightPointeri 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:

auto_ptr<wektor<>>

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&lt;*&gt;">
  <DisplayString>auto_ptr {*_Myptr}</DisplayString>
  <Expand>
    <ExpandedItem>_Myptr</ExpandedItem>
  </Expand>
</Type>

auto_ptr<wektor<int>> rozszerzenia ExpandedItem

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&lt;*,*&gt;">
  <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>

Współbieżność::Tablica z rozszerzeniem elementu syntetycznego

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&lt;*&gt;">
  <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 identyfikuje UIVisualizer. 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&lt;int,*&gt;">
  <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, IncludeViewi 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.