Partager via


Implémenter une fonction intrinsèque personnalisée NatVis pour C++

Dans cet article, vous allez découvrir les instructions relatives à l’implémentation d’une fonction intrinsèque personnalisée dans une visualisation NatVis. Pour plus d’informations sur NatVis, consultez Créer des vues personnalisées d’objets C++.

Syntaxe

Un fichier NatVis peut définir une fonction intrinsèque à l’aide de la syntaxe suivante :

<Intrinsic Name="Func" Expression="arg + 1">
  <Parameter Name="arg" Type="int" />
</Intrinsic>

Une fois la définition créée, toute expression de débogueur peut appeler la fonction, comme n’importe quelle autre fonction. Par exemple, à l’aide de la définition NatVis précédente, l’expression Func(3) prend la valeur 4.

Un élément <Intrinsic> peut apparaître au niveau du fichier ou à l’intérieur d’un élément <Type>, avant tout autre élément. Les fonctions intrinsèques définies dans un <Type> définissent les fonctions membres de ce type, tandis que les fonctions intrinsèques définies en dehors d’un <Type> définissent des fonctions globales. Par exemple:

<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
  <Type Name="std::vector&lt;*&gt;">
    <Intrinsic Name="size" Expression="_Mypair._Myval2._Mylast - _Mypair._Myval2._Myfirst" />
    <Intrinsic Name="capacity" Expression="_Mypair._Myval2._Myend - _Mypair._Myval2._Myfirst" />
  </Type>
  <Intrinsic Name="LOWORD" Expression="arg & 0xFFFF">
    <Parameter Name="arg" Type="unsigned int" />
  </Intrinsic>
</AutoVisualizer>

Dans l'exemple précédent, size() et capacity() sont définies en tant que fonctions membres de la classe std::vector. Par conséquent, chaque fois qu'une expression évalue vector.size(), elle évalue effectivement vector._Mypair._Myval2._Mylast - vector._Mypair._Myval2._Myfirst, plutôt que d'effectuer une fonction d'évaluation. De même, LOWORD(0x12345678) retourne 0x5678, également sans func-eval.

Pour voir un autre exemple, consultez l'expansion intrinsèque .

Instructions pour l’utilisation d’une fonction intrinsèque

Tenez compte des instructions suivantes lors de l’utilisation d’une fonction intrinsèque :

  • Les fonctions intrinsèques peuvent être surchargées, soit avec des fonctions définies par PDB, soit entre elles.

  • Lorsqu’une fonction intrinsèque est en conflit avec une fonction définie par PDB avec le même nom et la même liste d’arguments, la fonction intrinsèque gagne. Vous ne pouvez pas évaluer la fonction PDB si une fonction intrinsèque équivalente existe.

  • Vous ne pouvez pas prendre l’adresse d’une fonction intrinsèque ; vous pouvez seulement l’appeler.

  • Les fonctions membres intrinsèques doivent appartenir à la vue par défaut du type respectif afin d’être évaluées dans une expression. Par exemple, une entrée de type avec une contrainte de IncludeView peut ne pas spécifier de fonction intrinsèque.

  • Les fonctions intrinsèques peuvent être appelées à partir de n’importe quelle expression, y compris les expressions NatVis. Les fonctions intrinsèques peuvent également s’appeler mutuellement. Toutefois, les fonctions intrinsèques qui utilisent la récursivité ne sont actuellement pas prises en charge. Par exemple:

    <!-- OK -->
    <Intrinsic Name="Func2" Expression="this-&gt;Func3(arg + 1)">
      <Parameter Name="arg" Type="int" />
    </Intrinsic>
    <Intrinsic Name="Func3" Expression="arg + 1">
      <Parameter Name="arg" Type="int"/>
    </Intrinsic>
    
    <!-- Unsupported -->
    <Intrinsic Name="Func2" Expression="this-&gt;Func3(arg + 1)">
      <Parameter Name="arg" Type="int" />
    </Intrinsic>
    <Intrinsic Name="Func3" Expression="Func2(arg + 1)">
      <Parameter Name="arg" Type="int"/>
    </Intrinsic>
    
    <!-- Also unsupported-->
    <Intrinsic Name="Fib" Expression="(n &lt;= 1) ? 1 : Fib(n - 1) + Fib(n - 2)">
      <Parameter Name="n" Type="int"/>
    </Intrinsic>
    
  • Par défaut, les fonctions intrinsèques sont supposées être sans effet secondaire. Autrement dit, ils peuvent être appelés dans des contextes qui interdisent les effets secondaires, et l’expression d’implémentation n’est pas autorisée à contenir des effets secondaires.

    La définition peut remplacer ce comportement en spécifiant l’attribut SideEffect dans la déclaration. Si la fonction est marquée comme ayant des effets secondaires, les effets secondaires dans l’expression d’implémentation deviennent autorisés. Toutefois, l’appel de la fonction dans les contextes où les effets secondaires sont interdits n’est plus autorisé.

  • Lorsque les définitions de deux fonctions intrinsèques entrent en conflit (même nom, même signature), la dernière gagne (dans le même fichier).

    Dans différents fichiers, l’instance du fichier avec une priorité supérieure gagne (le projet est supérieur au répertoire utilisateur, ce qui est supérieur au répertoire d’installation). Si une définition de priorité supérieure contient une expression qui n’analyse pas, cette définition est ignorée et la définition de priorité la plus élevée suivante est utilisée à la place.

Instructions pour implémenter une fonction intrinsèque

Les fonctions intrinsèques prennent en charge deux formes d’implémentation possibles :

  • Basé sur une expression

    Le fichier NatVis définit une expression qui correspond à la valeur de retour de la fonction. L’expression peut utiliser tous les arguments passés dans l'expression et déclarés en tant qu'éléments <Parameter>. Les fonctions définies dans une classe sont également supposées être des fonctions « instance » et peuvent également accéder au pointeur « this ».

  • Basé sur une extension

    Le fichier NatVis fournit des instructions pour appeler une extension de débogueur pour évaluer réellement la fonction. Une extension de débogueur dispose d’un accès complet à l’API Concord et a la possibilité d’effectuer des tâches qui ne sont pas possibles dans une expression NatVis.

Pour fournir une implémentation basée sur une extension, l’élément <Intrinsic> doit omettre l’attribut Expression et, à la place, fournir SourceId, LanguageId, Idet ReturnType attributs, comme indiqué dans l’exemple suivant :

<Intrinsic Name="MyFunc" SourceId="a665fa54-6e7d-480e-a80b-1fc1202e9646" LanguageId="3a12d0b7-c26c-11d0-b442-00a0244a1dd2" Id="1000" ReturnType="double">
  <Parameter Type="int" />
  <Parameter Type="int" />
  <Parameter Type="int" />
</Intrinsic>

Pour implémenter la fonction, l’extension du débogueur doit implémenter l’interface IDkmIntrinsicFunctionEvaluator140, en utilisant les filtres LanguageId et SourceId qui correspondent aux valeurs correspondantes de l’élément <Intrinsic> dans le fichier NatVis. Lorsque la fonction est appelée, l’appel se traduit par la méthode Execute() du composant :

STDMETHOD(Execute)(
    _In_ Evaluation::IL::DkmILExecuteIntrinsic* pExecuteIntrinsic,
    _In_ Evaluation::DkmILContext* pILContext,
    _In_ Evaluation::IL::DkmCompiledILInspectionQuery* pInspectionQuery,
    _In_ const DkmArray<Evaluation::IL::DkmILEvaluationResult*>& Arguments,
    _In_opt_ DkmReadOnlyCollection<Evaluation::DkmCompiledInspectionQuery*>* pSubroutines,
    _Out_ DkmArray<Evaluation::IL::DkmILEvaluationResult*>* pResults,
    _Out_ Evaluation::IL::DkmILFailureReason* pFailureReason
    );

Le composant reçoit les octets de chaque argument via l’argument Arguments. Si la fonction en question est une fonction membre, le pointeur this vient en premier, suivi des arguments explicites. Le composant doit retourner le résultat en allouant un tableau à élément unique dans pResults, en stockant les octets de la valeur de retour.

Utilisez les instructions suivantes pour implémenter des fonctions :

  • Il est illégal de « mélanger et de faire correspondre » les deux formes d’implémentation. Autrement dit, vous ne pouvez pas inclure à la fois une expression et un ID source.

  • La spécification d’un type de retour pour une implémentation basée sur une expression est autorisée, mais pas obligatoire. Si un type de retour est spécifié, le type de retour de l’expression doit correspondre exactement (aucun cast implicite n’est autorisé). Si aucun type de retour n’est spécifié, le type de retour est déduit de l’expression. Toute implémentation basée sur une extension doit indiquer un type de retour dans le fichier NatVis.

  • Les appels non récursifs à d’autres fonctions intrinsèques sont autorisés. La récursivité n’est pas autorisée.

  • Si la fonction a des effets secondaires, vous devez spécifier SideEffect="true" dans la déclaration. Il est illégal pour une implémentation basée sur une expression d’avoir des effets secondaires dans l’expression sans déclarer la fonction comme ayant des effets secondaires. L’utilisation d’une implémentation basée sur l’extension pour provoquer des effets secondaires sans déclarer la fonction comme telles n’est pas défini et doit être évitée.

  • Les fonctions intrinsèques Varargs sont autorisées. Pour déclarer une fonction varargs, spécifiez Varargs="true" dans la déclaration. Bien qu’il soit légal pour une implémentation basée sur des expressions de déclarer une fonction vararg, actuellement, seules les implémentations basées sur des extensions ont un moyen d’accéder aux arguments de variable. Avec une implémentation basée sur l’extension, la fonction Execute() reçoit tous les arguments qui sont réellement passés, pas seulement les arguments déclarés.

  • Les fonctions intrinsèques consommant un type classe/struct/union comme type d’argument ne sont pas prises en charge. Le renvoi d’un type classe/struct/union est OK. (Un pointeur ou une référence à un type classe/struct/union est OK en tant que type d’argument).

Personnalisez l’icône pour les appels à des fonctions intrinsèques.

Par défaut, lorsque vous appelez une fonction intrinsèque, l’expression reçoit l’icône de diamant rose dans la fenêtre Watch associée aux appels de fonction. Vous pouvez remplacer ce comportement en spécifiant l’attribut Category à l’aide de l’une des valeurs suivantes :

  • Méthode. Utilisez l’icône de diamant rose, généralement utilisée avec les appels de méthode (par défaut).
  • Propriété. Utilisez l’icône de clé noire, habituellement associée aux propriétés.
  • Données. Utilisez l’icône de diamant bleu, généralement utilisée avec des données.

En combinant des fonctions intrinsèques avec l’élément <Item>, il est possible de créer un fichier NatVis où les expressions d’éléments affichent l’icône de la propriété clé à molette :

<Type Name="MyClass">
  <Intrinsic Name="GetValue" ReturnType="int" Expression="m_value" Category="Property" />
  <Expand>
    <Item Name="Value">this-&gt;GetValue()</Item>
  </Expand>
</Type>

Remarque

Placer le choix d’icônes au niveau de la fonction, plutôt que le niveau <Item>, évite les problèmes où la personnalisation de l’icône est perdue lorsque le nom complet est évalué. Étant donné que le nom complet inclut un appel à la fonction, il a la même personnalisation d’icône que le <Item> lui-même.