共用方式為


為 C++實作 NatVis 自定義內部函數

在本文中,您將瞭解在 NatVis 視覺效果中實作自定義內建函式的指導方針。 如需 NatVis 的詳細資訊,請參閱 建立 C++ 物件的自定義檢視

語法

NatVis 檔案可以使用下列語法來定義內建函式:

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

定義存在之後,任何調試程式表達式都可能會呼叫 函式,就像任何其他函式一樣。 例如,使用上述 NatVis 定義,表達式 Func(3) 評估為 4。

<Intrinsic> 元素可以出現在檔案層級上,或是在 <Type> 元素內,並在其他任何元素之前。 定義 <Type> 內建函式定義該型別的成員函式,而定義於 <Type> 外部的內建函式則定義全域函式。 例如:

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

在上述範例中,size()capacity() 定義為 std::vector 類別的成員函式,因此每當表達式評估 vector.size()時,它實際上就會評估 vector._Mypair._Myval2._Mylast - vector._Mypair._Myval2._Myfirst,而不是執行 func-eval。 同樣地,LOWORD(0x12345678) 傳回 0x5678,同時不使用 func-eval。

如需另一個範例,請參閱 內建擴充

使用內部函數的指導方針

使用內部函數時,請注意下列指導方針:

  • 內部函數可以重載,不論是與 PDB 定義的函數重載,或是彼此間重載。

  • 當內部函數與具有相同名稱和自變數清單的 PDB 定義函式衝突時,內部函數將會獲勝。 如果相等的內建函式存在,就無法對 PDB 函式進行運算評估。

  • 您無法取得內部函數的位址;您只能呼叫它。

  • 內建成員函式必須屬於對應類型的預設視圖,才能在表達式中被運算。 例如,具有 IncludeView 條件約束的類型專案可能不會指定內部函數。

  • 內部函數可以從任何表達式呼叫,包括 NatVis 表達式。 內部函數也可以彼此呼叫。 不過,目前不支援使用遞歸的內部函數。 例如:

    <!-- 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>
    
  • 根據預設,內部函數會假設為無副作用。 也就是說,可以在不允許副作用的內容中呼叫它們,而且不允許實作表達式包含副作用。

    定義可以藉由在宣告中指定 SideEffect 屬性來覆寫此行為。 如果函式標示為有副作用,則允許實作表達式中的副作用。 不過,不再允許在禁止副作用的內容中叫用 函式。

  • 當兩個內部函數的定義彼此衝突時(同名、同一個簽章),最後一個函式就會獲勝(在同一個檔案內)。

    在不同的檔案中,具有較高優先順序的檔案實例會獲勝(專案高於使用者目錄,高於安裝目錄)。 如果較高優先順序的定義包含未剖析的表達式,則會忽略該定義,並改用下一個最高的優先順序定義。

實作內建函式的指導方針

內部函數支援兩種可能的實作形式:

  • 以表達式為基礎的

    NatVis 檔案會定義一個表達式,該表達式解析為函式的傳回值。 表達式可以使用任何傳入的引數,這些引數被宣告為 <Parameter> 元素。 類別內定義的函式也會假設為 「實例」函式,而且也可以存取 「this」 指標。

  • 延伸型

    NatVis 檔案提供叫用調試程式延伸模組以實際評估函式的指示。 調試程式延伸模組具有 Concord API 的完整存取權,而且能夠執行 NatVis 運算式內無法執行的工作。

若要提供以延伸為基礎的實作,<Intrinsic> 元素應該省略 Expression 屬性,而是提供 SourceIdLanguageIdIdReturnType 屬性,如下列範例所示:

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

若要實作 函式,調試程式擴充功能必須使用符合 NatVis 檔案中 <Intrinsic> 元素對應值的 LanguageIdSourceId 篩選來實作 IDkmIntrinsicFunctionEvaluator140 介面。 呼叫 函式時,呼叫會轉譯為元件的 Execute() 方法:

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

元件會透過 Arguments 參數接收每個參數的位元組。 如果所討論的函式是成員函式,則 this 指標會先出現,然後是顯式的參數。 元件應該透過在 pResults中配置單一元素的陣列,來傳回結果,並儲存傳回值的位元組。

使用下列指南來撰寫函式:

  • “混搭”這兩種實作形式是非法的。 也就是說,您無法同時包含運算式和來源標識碼。

  • 允許指定表達式型實作的傳回型別,但不需要。 如果指定傳回型別,表達式的傳回型別必須完全符合它(不允許隱含轉換)。 如果未指定傳回型別,則會從表達式推斷傳回型別。 任何擴充型實作都必須在 NatVis 檔案中陳述傳回類型。

  • 允許對其他內部函數的非遞歸呼叫。 不允許遞歸。

  • 如果函式有副作用,您必須在宣告中指定 SideEffect="true"。 表達式型實作在表達式中具有副作用並不合法,而不需要宣告函式具有副作用。 叫用基於擴充的實作來產生副作用而不宣告函式具有副作用,是未定義行為,應予以避免。

  • 允許 Varargs 內部函數。 若要宣告 varargs 函式,請在宣告中指定 Varargs="true"。 雖然表達式型實作宣告 vararg 函式是合法的,但目前只有擴充型實作有方法可存取變數自變數。 使用擴充型實作時,Execute() 函式會接收實際傳入的所有自變數,而不只是宣告的自變數。

  • 不支援使用類別/結構/等位型別做為自變數類型的內建函式。 傳回類別/結構/聯合體類型是可以的。 (類別/結構/共用體類型的指標或參考是可以作為參數類型的)。

自定義呼叫內建函式的圖示。

根據預設,當您呼叫內建函式時,表達式會在與函數調用相關聯的 Watch 視窗中提供粉紅色菱形圖示。 您可以使用下列其中一個值來指定 Category 屬性,以覆寫此行為:

  • 方法。 使用粉紅色菱形圖示,通常與方法呼叫搭配使用(預設值)。
  • 財產。 使用通常與屬性搭配的黑色扳手圖示。
  • 數據。 使用藍色菱形圖示,通常與數據搭配使用。

藉由結合內建函式與 <Item> 元素,即可撰寫 NatVis 檔案,其中項目表達式具有扳手屬性圖示:

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

注意

將圖示選擇放在函式層級,而不是 <Item> 層級,可避免在評估完整名稱時遺失圖示自定義的問題。 因為完整名稱包含對函式的呼叫,所以其圖示自定義與 <Item> 本身相同。