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.
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é.
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 :
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.
Dans la boîte de dialogue Ajouter un nouvel élément, sélectionnezVisual C++>Utilitaire>Fichier de visualisation du débogueur (.natvis).
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 :
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.
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.
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>
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 :
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é.
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.
Les fichiers .natvis installés et inscrits à partir d’un package VSIX.
- Le répertoire Natvis propre à l’utilisateur (par exemple, %USERPROFILE%\Documents\Visual Studio 2022\Visualizers).
- Le répertoire Natvis propre à l’utilisateur (par exemple, %USERPROFILE%\Documents\Visual Studio 2019\Visualizers).
- 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 \>\> 24),x\</Item\>
\<Item Name="HiByteStatus" Condition="(_flags \& 0xFF000000) == 0"\>"None"\</Item\>
\<Item Name="HiByteStatus" Condition="(_flags \& 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<*>">
<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) :
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 :
Le type pour lequel la visualisation doit être utilisée (l’attribut
Name
).la valeur à laquelle doit ressembler un objet de ce type (élément
DisplayString
) ;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<*>">
<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<*>" 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>
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<*>">
<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<*>">
<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 :
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<wchar_t,*>">
<DisplayString>{m_pszData,su}</DisplayString>
</Type>
L’objet CStringT
s’affiche dans une fenêtre de variable, comme dans cet exemple :
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<wchar_t,*>">
<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.
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 :
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<*>">
<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 :
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 quevoid*
).
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<*,*>">
<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.
- Dans l’exemple précédent, l’expression
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],...
- , par exemple
- Dans l’exemple précédent, toutes les dimensions commencent à 0. Toutefois, si vous avez
Voici ce à quoi un objet Concurrency::array
bidimensionnel ressemble dans la fenêtre du débogueur :
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<*>">
<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<*,*>">
<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<*,*,*,*>">
<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>
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<*>">
<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 :
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<*>">
<DisplayString>auto_ptr {*_Myptr}</DisplayString>
<Expand>
<ExpandedItem>_Myptr</ExpandedItem>
</Expand>
</Type>
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<*,*>">
<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>
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<*>">
<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 unUIVisualizer
.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 :
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<int,*>">
<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.