Condividi tramite


Implementare la funzione intrinseca personalizzata NatVis per C++

In questo articolo vengono fornite informazioni sulle linee guida per l'implementazione di una funzione intrinseca personalizzata all'interno di una visualizzazione NatVis. Per altre informazioni su NatVis, vedere Creare viste personalizzate degli oggetti C++.

Sintassi

Un file NatVis può definire una funzione intrinseca usando la sintassi seguente:

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

Una volta creata la definizione, qualsiasi espressione del debugger può chiamare la funzione, come qualsiasi altra funzione. Ad esempio, usando la definizione NatVis precedente, l'espressione Func(3) restituisce 4.

Un elemento <Intrinsic> può essere visualizzato a livello di file o all'interno di un elemento <Type>, prima di qualsiasi altro elemento. Le funzioni intrinseche definite all'interno di un <Type> definiscono le funzioni membro di tale tipo, mentre le funzioni intrinseche definite all'esterno di un <Type> definiscono funzioni globali. Per esempio:

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

Nell'esempio precedente, size() e capacity() vengono definiti come funzioni membro della classe std::vector, quindi ogni volta che un'espressione valuta vector.size(), in realtà valuterà vector._Mypair._Myval2._Mylast - vector._Mypair._Myval2._Myfirst, anziché eseguire un func-eval. Analogamente, LOWORD(0x12345678) restituisce 0x5678, anche senza func-eval.

Per un altro esempio, vedere l'espansione intrinseca .

Linee guida per l'uso di una funzione intrinseca

Tenere presente le linee guida seguenti quando si usa una funzione intrinseca:

  • Le funzioni intrinseche possono essere sovraccaricate, sia con funzioni definite dal PDB, sia tra di loro.

  • Quando una funzione intrinseca è in conflitto con una funzione definita da PDB con lo stesso nome e lo stesso elenco di argomenti, la funzione intrinseca vincerà. Non è possibile valutare funzionalmente la funzione PDB se esiste una equivalente funzione intrinseca.

  • Non è possibile ottenere l'indirizzo di una funzione intrinseca; si può solo chiamarla.

  • Le funzioni membro intrinseche devono appartenere alla visualizzazione predefinita del rispettivo tipo per essere valutate in un'espressione. Ad esempio, una voce di tipo con un vincolo IncludeView potrebbe non specificare una funzione intrinseca.

  • Le funzioni intrinseche possono essere chiamate da qualsiasi espressione, incluse le espressioni NatVis. Le funzioni intrinseche possono anche chiamarsi tra loro. Tuttavia, le funzioni intrinseche che usano la ricorsione non sono attualmente supportate. Per esempio:

    <!-- 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>
    
  • Per impostazione predefinita, si presuppone che le funzioni intrinseche siano prive di effetti collaterali. Ovvero, possono essere chiamati in contesti che non consentono effetti collaterali e l'espressione di implementazione non può contenere effetti collaterali.

    La definizione può eseguire l'override di questo comportamento specificando l'attributo SideEffect nella dichiarazione. Se la funzione è contrassegnata come con effetti collaterali, gli effetti collaterali nell'espressione di implementazione diventano consentiti. Tuttavia, non è più consentito richiamare la funzione nei contesti in cui sono vietati gli effetti collaterali.

  • Quando le definizioni di due funzioni intrinseche sono in conflitto tra loro (stesso nome, stessa firma), l'ultima prevale (all'interno dello stesso file).

    All'interno di file diversi, l'istanza del file con priorità più alta prevale (il progetto è superiore alla directory utente, che è superiore alla directory di installazione). Se una definizione con priorità più alta contiene un'espressione che non analizza, tale definizione viene ignorata e viene invece usata la definizione con priorità più alta successiva.

Linee guida per implementare una funzione intrinseca

Le funzioni intrinseche supportano due possibili forme di implementazione:

  • Basato su espressioni

    Il file NatVis definisce un'espressione che restituisce il valore restituito della funzione. L'espressione può usare qualsiasi argomento passato ad essa dichiarato come elementi <Parameter>. Le funzioni definite all'interno di una classe vengono considerate anche funzioni di "istanza" e possono accedere anche al puntatore "this".

  • Basato sull'estensione

    Il file NatVis fornisce istruzioni per richiamare un'estensione del debugger per valutare effettivamente la funzione. Un'estensione del debugger ha accesso completo all'API Concord e ha la possibilità di eseguire attività che non sono possibili all'interno di un'espressione NatVis.

Per fornire un'implementazione basata su estensione, l'elemento <Intrinsic> deve omettere l'attributo Expression e, in alternativa, fornire SourceId, LanguageId, Ide ReturnType attributi, come illustrato nell'esempio seguente:

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

Per implementare la funzione, l'estensione del debugger deve implementare l'interfaccia IDkmIntrinsicFunctionEvaluator140, usando filtri LanguageId e SourceId che corrispondono ai valori corrispondenti dell'elemento <Intrinsic> nel file NatVis. Quando viene chiamata la funzione, la chiamata viene convertita nel metodo Execute() del componente:

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

Il componente riceve i byte di ciascun argomento per mezzo dell'argomento Arguments. Se la funzione in questione è una funzione membro, il puntatore this viene prima, seguito dagli argomenti espliciti. Il componente deve restituire il risultato allocando una matrice a singolo elemento in pResults, archiviando i byte del valore restituito.

Usare le linee guida seguenti per implementare le funzioni:

  • È illegale "combinare e abbinare" le due forme di implementazione. Ovvero, non è possibile includere sia un'espressione che un ID di origine.

  • È consentito specificare un tipo restituito per un'implementazione basata su espressioni, ma non obbligatorio. Se viene specificato un tipo restituito, il tipo restituito dell'espressione deve corrispondere esattamente (nessun cast implicito consentito). Se non viene specificato un tipo restituito, il tipo restituito viene dedotto dall'espressione. Qualsiasi implementazione basata su estensione deve dichiarare un tipo restituito nel file NatVis.

  • Sono consentite chiamate non ricorsive ad altre funzioni intrinseche. La ricorsione non è consentita.

  • Se la funzione presenta effetti collaterali, è necessario specificare SideEffect="true" nella dichiarazione. È illegale che un'implementazione basata su espressioni abbia effetti collaterali nell'espressione senza dichiarare che la funzione abbia effetti collaterali. Richiamare un'implementazione basata su un'estensione per avere effetti collaterali senza dichiarare che la funzione ha effetti collaterali è un comportamento indefinito e deve essere evitato.

  • Le funzioni intrinseche varargs sono consentite. Per dichiarare una funzione varargs, specificare Varargs="true" nella dichiarazione. Sebbene sia legale per un'implementazione basata su espressioni dichiarare una funzione vararg, attualmente solo le implementazioni basate su estensione hanno un modo per accedere agli argomenti delle variabili. Con un'implementazione basata su estensione, la funzione Execute() riceve tutti gli argomenti effettivamente passati, non solo gli argomenti dichiarati.

  • Le funzioni intrinseche che usano un tipo classe/struct/union come tipo di argomento non sono supportate. La restituzione di un tipo di classe/struct/unione è OK. Un puntatore o un riferimento a un tipo di classe/struct/union è accettabile come tipo di argomento.

Personalizzare l'icona per le chiamate alle funzioni intrinseche.

Per impostazione predefinita, quando si chiama una funzione intrinseca, all'espressione viene assegnata l'icona a forma di rombo rosa nella finestra Osserva associata alle chiamate di funzione. È possibile eseguire l'override di questo comportamento specificando l'attributo Category usando uno dei valori seguenti:

  • Metodo. Usare l'icona a forma di rombo rosa, in genere usata con le chiamate al metodo (impostazione predefinita).
  • Proprietà. Usare l'icona a forma di chiave nera, in genere usata con le proprietà.
  • Dati. Usare l'icona a forma di rombo blu, in genere usata con i dati.

Combinando funzioni intrinseche con l'elemento <Item>, è possibile creare un file NatVis in cui le espressioni di elemento hanno l'icona della proprietà wrench:

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

Nota

Posizionare la scelta dell'icona a livello di funzione, anziché il livello di <Item>, evita problemi in cui la personalizzazione dell'icona viene persa quando viene valutato il nome completo. Poiché il nome completo include una chiamata alla funzione, ha la stessa personalizzazione dell'icona della <Item> stessa.