Créer des vues personnalisées d’objets C++ dans le débogueur en utilisant le framework Natvis

Le framework Natvis de Visual Studio personnalise la façon dont les types natifs s’affichent dans les fenêtres de variable du débogueur, comme les fenêtres Variables locales et Espion, et dans DataTips. Les visualisations Natvis peuvent vous aider à rendre plus visibles les types que vous créez pendant le débogage.

Natvis remplace le fichier autoexp.dat utilisé dans les versions antérieures de Visual Studio, avec en plus la syntaxe XML, de meilleurs diagnostics, le versioning et la prise en charge de plusieurs fichiers.

Remarque

Les personnalisations Natvis fonctionnent avec des classes et des structs, mais pas des typedefs.

Visualisations Natvis

Vous pouvez utiliser le framework Natvis pour créer des règles de visualisation pour les types que vous créez, afin que les développeurs puissent les voir plus facilement pendant le débogage.

Par exemple, l’illustration suivante montre une variable de type Windows::UI::XAML::Controls::TextBox dans une fenêtre de débogueur sans aucune visualisation personnalisée appliquée.

Visualisation par défaut de la zone de texte

La ligne en surbrillance montre la propriété Text de la classe TextBox . La hiérarchie de classes complexe rend difficile la recherche de cette propriété. Le débogueur ne sait pas interpréter le type de chaîne personnalisé, si bien que vous ne pouvez pas voir la chaîne contenue par la zone de texte.

Le même TextBox semble beaucoup plus simple dans la fenêtre de variable quand les règles du visualiseur Natvis personnalisées sont appliquées. Les membres importants de la classe s’affichent ensemble, et le débogueur montre la valeur de chaîne sous-jacente du type de chaîne personnalisé.

Données de la zone de texte en utilisant le visualiseur

Utiliser des fichiers .natvis dans des projets C++

Natvis utilise des fichiers .natvis pour spécifier des règles de visualisation. Un fichier .natvis est un fichier XML avec une extension .natvis. Le schéma Natvis est défini dans le <Dossier d’installation VS>\Xml\Schemas\1033\natvis.xsd.

La structure de base d’un fichier .natvis est un ou plusieurs éléments Type représentant des entrées de visualisation. Le nom complet de chaque élément Type est spécifié dans son attribut 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>

Visual Studio fournit certains fichiers .natvis dans le dossier <Dossier d’installation VS>\Common7\Packages\Debugger\Visualizers. Ces fichiers ont des règles de visualisation pour de nombreux types courants et peuvent servir d’exemples pour écrire des visualisations pour les nouveaux types.

Ajouter un fichier .natvis à un projet C++

Vous pouvez ajouter un fichier .natvis à un projet C++.

Pour ajouter un nouveau fichier .natvis :

  1. Sélectionnez le nœud de projet C++ dans l’Explorateur de solutions, puis sélectionnez Projet>Ajouter un nouvel élément, ou cliquez avec le bouton droit sur le projet et sélectionnez Ajouter>Nouvel élément.

    Si vous ne voyez pas tous les modèles d’élément, choisissez Afficher tous les modèles.

  2. Dans la boîte de dialogue Ajouter un nouvel élément, sélectionnezVisual C++>Utilitaire>Fichier de visualisation du débogueur (.natvis).

  3. Nommez le fichier et sélectionnez Ajouter.

    Le nouveau fichier est ajouté à l’Explorateur de solutions et s’ouvre dans le volet de document Visual Studio.

Le débogueur Visual Studio charge automatiquement les fichiers .natvis dans les projets C++ et, par défaut, les ajoute également dans le fichier .pdb pendant la génération du projet. Si vous déboguez l’application générée, le débogueur charge le fichier .natvis à partir du fichier .pdb, même si le projet n’est pas ouvert. Si vous ne souhaitez pas que le fichier .natvis soit ajouté au fichier .pdb, vous pouvez l’exclure du fichier .pdb généré.

Pour exclure un fichier .natvis d’un fichier .pdb :

  1. Sélectionnez le fichier .natvis dans l’Explorateur de solutions, sélectionnez l’icône Propriétés, ou cliquez avec le bouton droit sur le fichier et sélectionnez Propriétés.

  2. Déroulez la flèche à côté de Exclu de la génération et sélectionnez Oui, puis OK.

Remarque

Pour déboguer les projets exécutables, utilisez les éléments de solution afin d’héberger les fichiers .natvis qui ne sont pas dans le .pdb, car aucun projet C++ est disponible.

Remarque

Les règles Natvis chargées à partir d’un .pdb s’appliquent uniquement aux types dans les modules que le .pdb référence. Par exemple, si Module1.pdb a une entrée Natvis pour un type nommé Test, elle s’applique seulement à la classe Test dans Module1.dll. Si un autre module définit également une classe nommée Test, l’entrée Natvis de Module1.pdb ne s’y applique pas.

Pour installer et inscrire un fichier .natvis à partir d’un package VSIX :

Un package VSIX peut installer et inscrire des fichiers .natvis. Quel que soit l’emplacement d’installation, tous les fichiers .natvis inscrits sont automatiquement récupérés pendant le débogage.

  1. Ajoutez le fichier .natvis dans le package VSIX. Par exemple, pour le fichier projet suivant :

    <?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. Inscrivez le fichier .natvis dans le fichier 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>
    

Emplacements des fichiers Natvis

Vous pouvez ajouter des fichiers .natvis dans votre répertoire utilisateur ou dans un répertoire système si vous voulez qu’ils s’appliquent à plusieurs projets.

Les fichiers .natvis sont évalués dans l’ordre suivant :

  1. Les fichiers .natvis incorporés dans un .pdb que vous déboguez, sauf si un fichier du même nom existe dans le projet chargé.

  2. Les fichiers .natvis qui se trouvent dans un projet C++ chargé ou une solution de niveau supérieur. Ce groupe comprend tous les projets C++ chargés, y compris les bibliothèques de classes, mais pas les projets d’autres langages.

  3. Les fichiers .natvis installés et inscrits à partir d’un package VSIX.

  1. Le répertoire Natvis propre à l’utilisateur (par exemple, %USERPROFILE%\Documents\Visual Studio 2022\Visualizers).
  1. Le répertoire Natvis propre à l’utilisateur (par exemple, %USERPROFILE%\Documents\Visual Studio 2019\Visualizers).
  1. Le répertoire Natvis de l'ensemble du système (<dossier d'installation de Microsoft Visual Studio>\Common7\Packages\Debugger\Visualizers). Ce répertoire a les fichiers .natvis qui sont installés avec Visual Studio. Si vous avez des autorisations d’administrateur, vous pouvez ajouter des fichiers dans ce répertoire.

Modifier des fichiers .natvis pendant le débogage

Vous pouvez modifier un fichier .natvis dans l’IDE pendant le débogage de son projet. Ouvrez le fichier dans la même instance de Visual Studio que celle que vous utilisez pour le débogage, modifiez-le et enregistrez-le. Dès que le fichier est enregistré, les fenêtres Espion et Variables locales sont mises à jour pour refléter le changement.

Vous pouvez aussi ajouter ou supprimer des fichiers .natvis dans une solution que vous déboguez, et Visual Studio ajoute ou supprime les visualisations appropriées.

Vous ne pouvez pas mettre à jour les fichiers .natvis incorporés dans des fichiers .pdb pendant le débogage.

Si vous modifiez le fichier .natvis en dehors de Visual Studio, les changements ne prennent pas effet automatiquement. Pour mettre à jour les fenêtres du débogueur, vous pouvez réévaluer la commande .natvisreload dans la fenêtre Exécution. Le changement prend alors effet sans redémarrage de la session de débogage.

Utilisez également la commande .natvisreload pour mettre à niveau le fichier .natvis vers une version plus récente. Par exemple, le fichier .natvis est peut-être archivé dans le contrôle de code source et vous souhaitez récupérer les changements récents faits par quelqu’un d’autre.

Expressions et mise en forme

Les visualisations Natvis utilisent des expressions C++ pour spécifier les éléments de données à afficher. En plus des améliorations et des limitations des expressions C++ dans le débogueur, qui sont décrites dans Opérateur de contexte (C++), tenez compte des choses suivantes :

  • Les expressions Natvis sont évaluées dans le contexte de l'objet qui est visualisé, et non dans le frame de pile actuel. Par exemple, x dans une expression Natvis référence le champ nommé x dans l’objet qui est visualisé, et non une variable locale nommée x dans la fonction actuelle. Vous ne pouvez pas accéder aux variables locales dans des expressions Natvis, même si vous pouvez accéder aux variables globales.

  • Les expressions Natvis n’autorisent pas l’évaluation de fonction ni les effets secondaires. Les appels de fonction et les opérateurs d’assignation sont ignorés. Comme les fonctions intrinsèques du débogueur n'ont pas d'effets secondaires, elles peuvent librement être appelées à partir de toute expression Natvis, même si d'autres appels de fonction sont interdits.

  • Pour contrôler l’affichage d’une expression, vous pouvez utiliser un des spécificateurs de format décrits dans Spécificateurs de format en C++. Les spécificateurs de format sont ignorés quand l’entrée est utilisée en interne par Natvis, comme l’expression Size dans une expansion ArrayItems.

Remarque

Comme le document natvis est en XML, vos expressions ne peuvent pas utiliser directement les opérateurs esperluette, supérieur à, inférieur à ou de décalage. Vous devez échapper ces caractères dans le corps de l’élément et les instructions de condition. Par exemple :
\<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\>

Vues Natvis

Vous pouvez définir différentes vues Natvis pour afficher les types de différentes manières. Par exemple, voici une visualisation de std::vector qui définit une vue simplifiée nommée simple. Les éléments DisplayString et ArrayItems s’affichent dans la vue par défaut et la vue simple, tandis que les éléments [size] et [capacity] ne s’affichent pas dans la vue 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>

Dans la fenêtre Espion, utilisez le spécificateur de format ,view pour spécifier une autre vue. La vue simple s’affiche sous la forme vec,view(simple) :

Fenêtre Espion avec une vue simple

Erreurs Natvis

Quand le débogueur rencontre des erreurs dans une entrée de visualisation, il les ignore. Il affiche le type sous sa forme brute ou sélectionne une autre visualisation appropriée. Vous pouvez utiliser les diagnostics Natvis pour comprendre pourquoi le débogueur a ignoré une entrée de visualisation, et pour voir les erreurs de syntaxe et d’analyse sous-jacentes.

Pour activer les diagnostics Natvis :

  • Sous Outils>Options (ou Déboguer>Options) >Débogage>Fenêtre de sortie, définissez Messages de diagnostic Natvis (C++ uniquement) sur Erreur, Avertissement ou Détaillé, puis sélectionnez OK.

Les erreurs s’affichent dans la fenêtre Sortie.

Référence à la syntaxe Natvis

Les éléments et attributs suivants peuvent être utilisés dans le fichier Natvis.

Élément AutoVisualizer

L’élément AutoVisualizer est le nœud racine du fichier .natvis et contient l’attribut xmlns: de l’espace de noms.

<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
.
.
</AutoVisualizer>

L’élément AutoVisualizer peut avoir des enfants Type, HResult, UIVisualizer et CustomVisualizer.

Type, élément

Un Type de base ressemble à cet exemple :

<Type Name="[fully qualified type name]">
  <DisplayString Condition="[Boolean expression]">[Display value]</DisplayString>
  <Expand>
    ...
  </Expand>
</Type>

L’élément Type spécifie :

  1. Le type pour lequel la visualisation doit être utilisée (l’attribut Name).

  2. la valeur à laquelle doit ressembler un objet de ce type (élément DisplayString ) ;

  3. Ce à quoi les membres du type doivent ressembler quand l’utilisateur développe le type dans une fenêtre de variable (le nœud Expand).

Classes avec modèle

L’attribut Name de l’élément Type accepte un astérisque * comme caractère générique dans les noms des classes avec modèle.

Dans l’exemple suivant, la même visualisation est utilisée pour un objet CAtlArray<int> ou CAtlArray<float>. S’il existe une entrée de visualisation spécifique pour un CAtlArray<float>, alors elle a la priorité sur la générique.

<Type Name="ATL::CAtlArray&lt;*&gt;">
    <DisplayString>{{Count = {m_nSize}}}</DisplayString>
</Type>

Vous pouvez référencer des paramètres de modèle dans l’entrée de visualisation en utilisant les macros $T1, $T2, etc. Pour trouver des exemples de ces macros, consultez les fichiers .natvis fournis avec Visual Studio.

Correspondance des types de visualiseur

Si une entrée de visualisation ne peut pas être validée, la visualisation disponible suivante est utilisée.

Attribut pouvant être hérité

L’attribut facultatif Inheritable spécifie si une visualisation s’applique uniquement à un type de base, ou à un type de base et à tous les types dérivés. La valeur par défaut de Inheritable est true.

Dans l’exemple suivant, la visualisation s’applique uniquement au type BaseClass :

<Type Name="Namespace::BaseClass" Inheritable="false">
    <DisplayString>{{Count = {m_nSize}}}</DisplayString>
</Type>

Attribut de priorité

L’attribut Priority facultatif spécifie l’ordre dans lequel utiliser les autres définitions si une définition ne peut pas être analysée. Les valeurs possibles de Priority sont : Low, MediumLow, Medium, MediumHigh et High. La valeur par défaut est Medium. L’attribut Priority distingue uniquement les priorités au sein du même fichier .natvis.

L’exemple suivant analyse d’abord l’entrée qui correspond au STL 2015. Si l’analyse échoue, l’autre entrée pour la version 2013 de STL est utilisée :

<!-- 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>

Attribut Optional

Vous pouvez mettre un attribut Optional sur n’importe quel nœud. Si une sous-expression à l’intérieur d’un nœud facultatif ne peut pas être analysée, le débogueur ignore ce nœud, mais applique le reste des règles Type. Dans le type suivant, [State] est obligatoire, mais [Exception] est facultatif. Si MyNamespace::MyClass a un champ nommé _M_exceptionHolder, le nœud [State] et le nœud [Exception] s’affichent, mais s’il n’y a pas de champ _M_exceptionHolder, seul le nœud [State] s’affiche.

<Type Name="MyNamespace::MyClass">
    <Expand>
      <Item Name="[State]">_M_State</Item>
      <Item Name="[Exception]" Optional="true">_M_exceptionHolder</Item>
    </Expand>
</Type>

Attribut Condition

L’attribut Condition facultatif est disponible pour de nombreux éléments de visualisation et spécifie quand utiliser une règle de visualisation. Si l’expression dans l’attribut de condition prend la valeur false, la règle de visualisation ne s’applique pas. Si elle prend la valeur true ou qu’il n’existe aucun attribut Condition, la visualisation s’applique. Vous pouvez utiliser cet attribut pour une logique if-else dans les entrées de visualisation.

Par exemple, la visualisation suivante a deux éléments DisplayString pour un type de pointeur intelligent. Quand le membre _Myptr est vide, la condition du premier élément DisplayString prend la valeur true, et ce formulaire s’affiche. Quand le membre _Myptr n’est pas vide, la condition prend la valeur false et le deuxième élément DisplayString s’affiche.

<Type Name="std::auto_ptr&lt;*&gt;">
  <DisplayString Condition="_Myptr == 0">empty</DisplayString>
  <DisplayString>auto_ptr {*_Myptr}</DisplayString>
  <Expand>
    <ExpandedItem>_Myptr</ExpandedItem>
  </Expand>
</Type>

Attributs IncludeView et ExcludeView

Les attributs IncludeView et ExcludeView spécifient les éléments à afficher ou non dans des vues spécifiques. Par exemple, dans la spécification Natvis suivante de std::vector, la vue simple n’affiche pas les éléments [size] et [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>

Vous pouvez utiliser les attributs IncludeView et ExcludeView sur des types et des membres individuels.

élément Version

L’élément Version étend une entrée de visualisation à un module et une version spécifiques. L’élément Version permet d’éviter les collisions de noms, de réduire les incompatibilités involontaires et d’autoriser différentes visualisations pour différentes versions de type.

Si un fichier d’en-tête commun utilisé par différents modules définit un type, la visualisation versionée s’affiche uniquement quand le type se trouve dans la version de module spécifiée.

Dans l’exemple suivant, la visualisation est applicable uniquement au type DirectUI::Border qui figure dans le Windows.UI.Xaml.dll de la version 1.0 à 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>

Vous n’avez pas besoin de Min et Max. Ce sont des attributs facultatifs. Les caractères génériques ne sont pas pris en charge.

L’attribut Name est au format filename.ext, par exemple, hello.exe ou some.dll. Aucun nom de chemin n’est autorisé.

Élément DisplayString

L’élément DisplayString spécifie une chaîne à afficher comme valeur d’une variable. Il accepte les chaînes arbitraires mélangées à des expressions. Tout ce qui figure entre accolades est interprété comme une expression. Par exemple, l’entrée DisplayString suivante :

<Type Name="CPoint">
  <DisplayString>{{x={x} y={y}}}</DisplayString>
</Type>

Signifie que les variables de type CPoint s’affichent comme dans cette illustration :

Utiliser un élément DisplayString

Dans l’expression DisplayString, x et y, qui sont membres de CPoint, sont placés entre accolades, donc leurs valeurs sont évaluées. L’exemple montre également comment échapper une accolade en utilisant des accolades doubles ({{ ou }}).

Notes

L'élément DisplayString est le seul élément qui accepte des chaînes arbitraires et la syntaxe avec accolades. Tous les autres éléments de visualisation acceptent uniquement les expressions que le débogueur peut évaluer.

Élément StringView

L’élément StringView définit une valeur que le débogueur peut envoyer au visualiseur de texte intégré. Par exemple, supposons la visualisation suivante pour le type ATL::CStringT :

<Type Name="ATL::CStringT&lt;wchar_t,*&gt;">
  <DisplayString>{m_pszData,su}</DisplayString>
</Type>

L’objet CStringT s’affiche dans une fenêtre de variable, comme dans cet exemple :

Élément CStringT DisplayString

L’ajout d’un élément StringView indique au débogueur qu’il peut afficher la valeur sous forme de visualisation de texte.

<Type Name="ATL::CStringT&lt;wchar_t,*&gt;">
  <DisplayString>{m_pszData,su}</DisplayString>
  <StringView>m_pszData,su</StringView>
</Type>

Pendant le débogage, vous pouvez sélectionner l’icône en forme de loupe à côté de la variable, puis sélectionner Visualiseur de texte pour afficher la chaîne vers laquelle pointe m_pszData.

Données CStringT avec le visualiseur StringView

L’expression {m_pszData,su} comprend un spécificateur de format C++, su, pour afficher la valeur sous forme de chaîne Unicode. Pour plus d’informations, consultez Spécificateurs de format en C++.

Élément Expand

Le nœud facultatif Expand personnalise les enfants d’un type visualisé quand vous développez le type dans une fenêtre de variable. Le nœud Expand accepte la liste des nœuds enfants qui définissent les éléments enfants.

  • Si un nœud Expand n’est pas spécifié dans une entrée de visualisation, les enfants utilisent les règles d’expansion par défaut.

  • Si un nœud Expand est spécifié sans aucun nœud enfant en dessous, le type n’est pas développé dans les fenêtres du débogueur.

Expansion d'éléments

L’élément Item est le plus basique et le plus courant des éléments dans un nœud Expand. Item définit un seul élément enfant. Par exemple, une classe CRect avec des champs top, left, right et bottom a l’entrée de visualisation suivante :

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

Dans la fenêtre du débogueur, le type CRect ressemble à cet exemple :

CRect avec expansion d’élément Item

Le débogueur évalue les expressions spécifiées dans les éléments Width et Height, et affiche les valeurs dans la colonne Valeur de la fenêtre de variable.

Le débogueur crée automatiquement le nœud [Raw View] pour chaque expansion personnalisée. La capture d’écran précédente montre le nœud [Raw View] développé, pour montrer en quoi la vue brute par défaut de l’objet diffère de sa visualisation Natvis. L’expansion par défaut crée une sous-arborescence pour la classe de base et liste tous les membres de données de la classe de base comme des enfants.

Notes

Si l’expression de l’élément item pointe vers un type complexe, le nœud Item lui-même peut être développé.

ArrayItems expansion

Utilisez le nœud ArrayItems pour que le débogueur Visual Studio interprète le type comme un tableau et en affiche les éléments individuels. La visualisation pour std::vector est un bon exemple :

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

Un std::vector montre ses éléments individuels quand il est développé dans la fenêtre de variables :

sstd::vector utilisant l’expansion ArrayItems

Le nœud ArrayItems doit avoir :

  • une expression Size (qui doit prendre la valeur d’un entier) pour que le débogueur comprenne la longueur du tableau.
  • Une expression ValuePointer qui pointe vers le premier élément (qui doit être le pointeur d’un type d’élément autre que void*).

La valeur par défaut de la limite inférieure du tableau est 0. Pour remplacer la valeur, utilisez un élément LowerBound. Les fichiers .natvis fournis avec Visual Studio ont des exemples.

Notes

Vous pouvez utiliser l’opérateur [], par exemple, vector[i], avec n’importe quelle visualisation de tableau unidimensionnel qui utilise ArrayItems, même si le type lui-même (par exemple, CATLArray) n’autorise pas cet opérateur.

Vous pouvez également spécifier des tableaux multidimensionnels. Dans ce cas, le débogueur a besoin d’un peu plus d’informations pour afficher correctement les éléments enfants :

<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 indique si le tableau suit l’ordre row-major ou column-major.
  • Rank spécifie le rang du tableau.
  • L’élément Size accepte le paramètre $i implicite qu’il remplace par l’index de dimension pour déterminer la longueur du tableau dans cette dimension.
    • Dans l’exemple précédent, l’expression _M_extent.M_base[0] doit indiquer la longueur de la dimension 0, _M_extent._M_base[1] le première, etc.
  • LowerBound indique la limite inférieure de chaque dimension du tableau. Pour les tableaux multidimensionnels, vous pouvez spécifier une expression qui utilise le paramètre implicite $i. Le paramètre $i est remplacé par l’index de dimension pour trouver la limite inférieure du tableau dans cette dimension.
    • Dans l’exemple précédent, toutes les dimensions commencent à 0. Toutefois, si vous avez ($i == 1) ? 1000 : 100 comme limite inférieure, la dimension 0 commence à 100, et la première dimension commence à 1 000.
      • , par exemple [100, 1000], [100, 1001], [100, 1002], ... [101, 1000], [101, 1001],...

Voici ce à quoi un objet Concurrency::array bidimensionnel ressemble dans la fenêtre du débogueur :

Tableau bidimensionnel avec expansion ArrayItems

Expansion d'IndexListItems

Vous pouvez utiliser l’expansion ArrayItems uniquement si les éléments du tableau sont disposés de façon contiguë en mémoire. Le débogueur accède à l’élément suivant en incrémentant simplement son pointeur. Si vous devez manipuler l’index sur le nœud de valeur, utilisez des nœuds IndexListItems. Voici une visualisation avec un nœud 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>

La seule différence entre ArrayItems et IndexListItems est le ValueNode, qui attend l’expression complète de l’élément ième avec le paramètre $i implicite.

Notes

Vous pouvez utiliser l’opérateur [], par exemple, vector[i], avec n’importe quelle visualisation de tableau unidimensionnel qui utilise IndexListItems, même si le type lui-même (par exemple, CATLArray) n’autorise pas cet opérateur.

Expansion de LinkedListItems

Si le type visualisé représente une liste liée, le débogueur peut afficher ses enfants à l'aide d'un nœud LinkedListItems . La visualisation suivante du type CAtlList utilise 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>

L'élément Size fait référence à la longueur de la liste. HeadPointer pointe vers le premier élément, NextPointer fait référence à l'élément suivant et ValueNode fait référence à la valeur de l'élément.

Le débogueur évalue les expressions NextPointer et ValueNode dans le contexte de l’élément de nœud LinkedListItems, pas du type de la liste parente. Dans l’exemple précédent, CAtlList a une classe CNode (située dans atlcoll.h) qui est un nœud de la liste liée. m_pNext et m_element sont des champs de cette classe CNode et non de la classe CAtlList.

ValueNode peut être laissé vide ou utiliser this pour référencer le nœud LinkedListItems lui-même.

Expansion CustomListItems

L'expansion CustomListItems vous permet d'écrire une logique personnalisée permettant de parcourir une structure de données telle qu'une table de hachage. Utilisez CustomListItems afin de visualiser des structures de données qui peuvent utiliser des expressions C++ pour tout ce que vous devez évaluer, mais qui ne correspondent pas tout à fait au moule de ArrayItems, IndexListItems ou LinkedListItems.

Vous pouvez utiliser Exec pour exécuter du code à l’intérieur d’une expansion CustomListItems, en utilisant les variables et les objets définis dans l’expansion. Vous pouvez utiliser des opérateurs logiques, des opérateurs arithmétiques et des opérateurs d’assignation avec Exec. Vous ne pouvez pas utiliser Exec pour évaluer des fonctions, sauf les fonctions intrinsèques du débogueur prises en charge par l’évaluateur d’expression C++.

Le visualiseur suivant pour CAtlMap est un excellent exemple où CustomListItems est approprié.

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

Expansion de TreeItems

Si le type visualisé représente une arborescence, le débogueur peut la parcourir et afficher ses enfants à l'aide d'un nœud TreeItems . Voici la visualisation du type std::map utilisant un nœud 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>

La syntaxe est similaire à celle du nœud LinkedListItems. LeftPointer, RightPointer et ValueNode sont évalués dans le contexte de la classe de nœud d’arborescence. ValueNode peut être laissé vide ou utiliser this pour référencer le nœud TreeItems lui-même.

Expansion d'ExpandedItem

L’élément ExpandedItem génère une vue enfant agrégée en affichant les propriétés des classes de base ou des membres de données comme s’ils étaient des enfants du type visualisé. Le débogueur évalue l’expression spécifiée et ajoute les nœuds enfants du résultat à la liste enfant du type visualisé.

Par exemple, le type de pointeur intelligent auto_ptr<vector<int>> s’affiche généralement de la façon suivante :

auto_ptr<vector<int>> expansion par défaut

Pour voir les valeurs du vecteur, vous devez descendre de deux niveaux dans la fenêtre de variable, en passant par le membre _Myptr. En ajoutant un élément ExpandedItem , vous pouvez éliminer la variable _Myptr de la hiérarchie et afficher directement les éléments du vecteur :

<Type Name="std::auto_ptr&lt;*&gt;">
  <DisplayString>auto_ptr {*_Myptr}</DisplayString>
  <Expand>
    <ExpandedItem>_Myptr</ExpandedItem>
  </Expand>
</Type>

auto_ptr<vector<int>> Expansion ExpandedItem

L’exemple suivant montre comment agréger des propriétés de la classe de base dans une classe dérivée. Supposons que la classe CPanel dérive de la classe CFrameworkElement. Au lieu de répéter les propriétés provenant de la classe CFrameworkElement de base, la visualisation du nœud ExpandedItem ajoute ces propriétés à la liste enfant de la classe CPanel.

<Type Name="CPanel">
  <DisplayString>{{Name = {*(m_pstrName)}}}</DisplayString>
  <Expand>
    <Item Name="IsItemsHost">(bool)m_bItemsHost</Item>
    <ExpandedItem>*(CFrameworkElement*)this,nd</ExpandedItem>
  </Expand>
</Type>

Le spécificateur de format nd qui désactive l’association de la visualisation de la classe dérivée est ici nécessaire. Sinon, l’expression *(CFrameworkElement*)this entraîne une nouvelle application de la visualisation CPanel, car les règles de correspondance du type de visualisation par défaut la considèrent comme la plus appropriée. Utilisez le spécificateur de format nd pour indiquer au débogueur d’utiliser la visualisation de la classe de base, ou l’expansion par défaut si la classe de base n’a pas de visualisation.

Expansion de l’élément Synthetic

Alors que l’élément ExpandedItem offre une vue plus plate des données en éliminant les hiérarchies, le nœud Synthetic fait exactement le contraire. Il vous permet de créer un élément enfant artificiel qui n’est pas le résultat d’une expression. L’élément artificiel peut avoir ses propres éléments enfants. Dans l'exemple suivant, la visualisation du type Concurrency::array utilise un nœud Synthetic pour présenter un message de diagnostic à l'utilisateur :

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

Concurrency::Array avec expansion de l’élément Synthetic

Expansion intrinsèque

Fonction intrinsèque personnalisée qui peut être appelée à partir d’une expression. Un élément <Intrinsic> doit être accompagné d’un composant de débogueur qui implémente la fonction via l’interface IDkmIntrinsicFunctionEvaluator140.

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

Élément HResult

L’élément HResult vous permet de personnaliser les informations affichées pour un HRESULT dans les fenêtres du débogueur. L’élément HRValue doit contenir la valeur 32 bits du HRESULT à personnaliser. L’élément HRDescription contient les informations à afficher dans la fenêtre du débogueur.


<HResult Name="MY_E_COLLECTION_NOELEMENTS">
  <HRValue>0xABC0123</HRValue>
  <HRDescription>No elements in the collection.</HRDescription>
</HResult>

Élément UIVisualizer

Un élément UIVisualizer permet d'inscrire un plug-in de visualiseur graphique auprès du débogueur. Un visualiseur graphique crée une boîte de dialogue ou une autre interface qui montre une variable ou un objet de manière cohérente avec son type de données. Le plug-in du visualiseur doit être créé comme un VSPackage et doit exposer un service consommable par le débogueur. Le fichier .natvis contient les informations d’inscription du plug-in, comme son nom, l’identificateur global unique du service exposé et les types qu’il peut visualiser.

Voici un exemple d'élément UIVisualizer :

<?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>
  • Une paire d’attributs ServiceId - Id identifie un UIVisualizer. ServiceId est le GUID du service exposé par le package du visualiseur. Id est un identificateur unique qui différencie les visualiseurs, si un service en fournit plusieurs. Dans l’exemple précédent, le même service de visualiseur fournit deux visualiseurs.

  • L’attribut MenuName définit un nom de visualiseur à afficher dans la liste déroulante à côté de l’icône de loupe dans le débogueur. Par exemple :

    Menu contextuel du menu UIVisualizer

Chaque type défini dans le fichier .natvis doit explicitement lister les visualiseurs d’interface utilisateur capables de l’afficher. Le débogueur fait correspondre les références de visualiseur des entrées de type aux visualiseurs inscrits. Par exemple, l’entrée de type suivante pour std::vector référence le UIVisualizer de l’exemple précédent.

<Type Name="std::vector&lt;int,*&gt;">
  <UIVisualizer ServiceId="{5452AFEA-3DF6-46BB-9177-C0B08F318025}" Id="1" />
</Type>

Vous pouvez voir un exemple de UIVisualizer dans l’extension Image Watch utilisée pour voir des images bitmap en mémoire.

Élément CustomVisualizer

CustomVisualizer est un point d’extensibilité qui spécifie une extension VSIX que vous écrivez pour contrôler les visualisations dans le code Visual Studio. Pour plus d’informations sur l’écriture d’extensions VSIX, consultez SDK Visual Studio.

Il faut beaucoup plus de travail pour écrire un visualiseur personnalisé qu’une définition Natvis XML, mais vous êtes libre de toute contrainte sur ce que Natvis prend ou non en charge. Les visualiseurs personnalisés ont accès à l’ensemble des API d’extensibilité du débogueur, qui peuvent interroger et modifier le processus de l’élément débogué, ou communiquer avec d’autres parties de Visual Studio.

Vous pouvez utiliser les attributs Condition, IncludeView et ExcludeView sur des éléments CustomVisualizer.

Limites

Les personnalisations Natvis fonctionnent avec des classes et des structs, mais pas des typedefs.

Natvis ne prend pas en charge les visualiseurs des types primitifs (par exemple, int, bool) ou des pointeurs vers les types primitifs. Dans ce scénario, vous pouvez utiliser le spécificateur de format approprié à votre cas d’usage. Par exemple, si vous utilisez double* mydoublearray dans votre code, vous pouvez utiliser un spécificateur de format de tableau dans la fenêtre Espion du débogueur, comme l’expression mydoublearray, [100], qui affiche les 100 premiers éléments.