共用方式為


調試程序數據模型C++概念

本主題描述調試程式C++數據模型 的概念。

數據模型中的概念

資料模型中的綜合對象實際上是兩個事項:

  • 鍵/值/元數據的元組字典。
  • 數據模型支援的一組概念(介面)。 概念是客戶端實作的介面(與數據模型相反),以提供一組指定的語意行為。 目前支援的一組概念列在這裡。
概念介面 說明
IDataModelConcept 這個概念是一個母模型。 如果此模型透過已註冊的類型簽章自動附加至原生類型,則每次具現化這類類型的新物件時,都會自動呼叫 InitializeObject 方法。
IStringDisplayableConcept(字串顯示概念) 對象可以轉換成字串以供顯示之用。
IIterableConcept 物件是容器,可以被遍歷。
可索引概念 物件是容器,可以在一或多個維度中索引(透過隨機存取)。
IPreferredRuntimeTypeConcept 物件對自身衍生的類型比底層類型系統能夠提供的還要瞭解,並希望自行處理從靜態型別到運行時型別的轉換。
動態密鑰提供者概念 物件是鍵的動態提供者,並希望從核心資料模型接管所有鍵查詢。 此介面通常用來作為連接動態語言的橋樑,例如 JavaScript。
IDynamicConceptProviderConcept 物件是概念的動態提供者,希望能接管核心資料模型中的所有概念查詢。 此介面通常用來作為與動態語言,例如 JavaScript 的橋樑。

數據模型概念:IDataModelConcept

附加至另一個模型物件做為父模型的任何模型物件,都必須直接支持數據模型概念。 數據模型概念需要支援介面,IDataModelConcept 的定義如下。

DECLARE_INTERFACE_(IDataModelConcept, IUnknown)
{
    STDMETHOD(InitializeObject)(_In_ IModelObject* modelObject, _In_opt_ IDebugHostTypeSignature* matchingTypeSignature, _In_opt_ IDebugHostSymbolEnumerator* wildcardMatches) PURE;
    STDMETHOD(GetName)(_Out_ BSTR* modelName) PURE;
}

InitializeObject

資料模型可以透過資料模型管理員的 RegisterModelForTypeSignature 或 RegisterExtensionForTypeSignature 方法,註冊為標準視覺化工具或用於特定原生類型的擴展。 透過上述任一方法註冊模型時,數據模型會自動作為父模型鏈接到任何其類型符合註冊中傳遞之識別符的本地物件。 在自動建立該附件時,會在數據模型上呼叫 InitializeObject 方法。 會傳遞到的是實例物件、引發附加的類型簽章,以及產生型別實例的列舉器(按線性順序),其匹配類型簽章中的任何通配符。 數據模型實作可能會使用這個方法呼叫來初始化它所需的任何快取。

GetName

如果指定的數據模型透過 RegisterNamedModel 方法以預設名稱註冊,則已註冊的數據模型 IDataModelConcept 介面必須從這個方法傳回該名稱。 請注意,模型可以在多個名稱下註冊是完全合法的(這裡應返回預設名稱或最佳名稱)。 模型可能完全未命名(只要未以名稱註冊)。 在這種情況下,GetName 方法應該會傳回E_NOTIMPL。

字串可顯示的概念:IStringDisplayableConcept

想要為顯示目的提供字串轉換的物件,可以透過實作 IStringDisplayableConcept 介面來實作字串可顯示的概念。 介面的定義如下:

DECLARE_INTERFACE_(IStringDisplayableConcept, IUnknown)
{
    STDMETHOD(ToDisplayString)(_In_ IModelObject* contextObject, _In_opt_ IKeyStore* metadata, _Out_ BSTR* displayString) PURE;
}

ToDisplayString

每當客戶端想要將物件轉換為顯示用的字串時,就會呼叫 ToDisplayString 方法(例如顯示在控制台、UI 等中)。這種字串轉換不應作為其他程式設計操作的基礎。 字串轉換本身可能會受到傳遞至呼叫的元數據的影響。 字串轉換應該儘量遵循 PreferredRadix 和 PreferredFormat 這兩個鍵的設定。

可反覆運算的概念:IIterableConcept 和 IModelIterator

物件是其他物件的容器,並希望表達逐一查看這些包含物件的能力,可藉由實作IIterableConcept和IModelIterator介面來支援可反覆運算的概念。 支援可反覆運算的概念和支援可編製索引的概念之間有一個非常重要的關聯性。 支援隨機存取自主物件的物件除了可反覆運算的概念之外,還可以支援可編製索引的概念。 在此情況下,Iterated 元素也必須產生預設索引,當傳遞至可編製索引的概念參考相同的物件時。 無法滿足此不因變數會導致偵錯主機中未定義的行為。

IIterableConcept 的定義如下:

DECLARE_INTERFACE_(IIterableConcept, IUnknown)
{
    STDMETHOD(GetDefaultIndexDimensionality)(_In_ IModelObject* contextObject, _Out_ ULONG64* dimensionality) PURE;
    STDMETHOD(GetIterator)(_In_ IModelObject* contextObject, _Out_ IModelIterator** iterator) PURE;
}

IModelIterator 概念的定義如下:

DECLARE_INTERFACE_(IModelIterator, IUnknown)
{
   STDMETHOD(Reset)() PURE;
   STDMETHOD(GetNext)(_COM_Errorptr_ IModelObject** object, _In_ ULONG64 dimensions, _Out_writes_opt_(dimensions) IModelObject** indexers, _COM_Outptr_opt_result_maybenull_ IKeyStore** metadata) PURE;
}

IIterableConcept 的 GetDefaultIndexDimensionality

GetDefaultIndexDimensionality 方法會將維度數目傳回預設索引。 如果對象無法編制索引,這個方法應該會傳回 0 並成功(S_OK)。 從這個方法傳回非零值的任何物件,都宣告對通訊協定合約的支援,該合約指出:

  • 物件透過 IIndexableConcept 的支援來支援可編製索引的概念
  • 從可反覆運算概念之 GetIterator 方法傳回之 IModelIterator 的 GetNext 方法會針對每個產生的專案傳回唯一的預設索引。 這類索引的維度數目將會如這裡所示。
  • 將IModelIterator的 GetNext 方法所傳回的索引傳遞至可索引概念上的 GetAt 方法 (IIndexableConcept) 會參考 GetNext 所產生的相同物件。 傳回相同的值。

IIterableConcept 的 GetIterator

可反覆運算概念上的 GetIterator 方法會傳回反覆運算器介面,可用來反覆運算物件。 傳回的反覆運算器必須記住傳遞至 GetIterator 方法的內容物件。 它不會傳遞至反覆運算器本身的方法。

IModelIterator 的 Reset

從可反覆運算概念傳回之反覆運算器上的 Reset 方法,會將反覆運算器的位置還原到反覆運算器第一次建立時的位置(在第一個元素之前)。 雖然強烈建議反覆運算器支援 Reset 方法,但並非必要。 反覆運算器可以是相當於C++輸入反覆運算器,而且只允許單一轉送反覆運算。 在這種情況下,Reset 方法可能會因為E_NOTIMPL而失敗。

IModelIterator 的 GetNext

GetNext 方法會將反覆運算器向前移動,並擷取下一個反覆運算的專案。 如果物件除了可反覆運算,而且這個物件是由傳回非零值的 GetDefaultIndexDimensionality 自變數所指出,這個方法可能會選擇性地傳回預設索引,以回到索引器產生的值。 請注意,呼叫端可以選擇傳遞 0/nullptr,而不會擷取任何索引。 呼叫端要求部分索引是非法的(例如:小於 GetDefaultIndexDimensionality 所產生的數位)。

如果反覆運算器成功向前移動,但讀取反覆運算項的值時發生錯誤,此方法可能會傳回錯誤 AND 以錯誤物件填滿 “object”。 在內含專案的反覆運算結束時,反覆運算器會從 GetNext 方法傳回E_BOUNDS。 任何後續的呼叫(除非有介入的重設呼叫),也會傳回E_BOUNDS。

可編製索引的概念:IIndexableConcept

想要對一組內容提供隨機存取的物件,可透過IIndexableConcept 介面的支援來支援可編製索引的概念。 大部分可編製索引的物件也會透過可反覆運算的概念支援來逐一查看。 不過,這不是必要的。 如果支援,反覆運算器和索引器之間有重要的關聯性。 反覆運算器必須支援 GetDefaultIndexDimensionality、從該方法傳回非零值,並支援該處記載的合約。 索引器概念介面的定義如下:

DECLARE_INTERFACE_(IIndexableConcept, IUnknown)
{
    STDMETHOD(GetDimensionality)(_In_ IModelObject* contextObject, _Out_ ULONG64* dimensionality) PURE;
    STDMETHOD(GetAt)(_In_ IModelObject* contextObject, _In_ ULONG64 indexerCount, _In_reads_(indexerCount) IModelObject** indexers, _COM_Errorptr_ IModelObject** object, _COM_Outptr_opt_result_maybenull_ IKeyStore** metadata) PURE;
    STDMETHOD(SetAt)(_In_ IModelObject* contextObject, _In_ ULONG64 indexerCount, _In_reads_(indexerCount) IModelObject** indexers, _In_ IModelObject *value) PURE;
}

使用索引器(及其與反覆運算器互動)的范例如下所示。 此範例會反覆運算可編製索引的容器內容,並使用索引器回到剛傳回的值。 雖然該作業在功能上是無用的,但會示範這些介面的互動方式。 請注意,下列範例不會處理記憶體配置失敗。 它會假設擲回新的 專案(視程序代碼所在的環境而定,這可能是一個不良假設 -- 數據模型的 COM 方法不能有C++例外狀況逸出):

ComPtr<IModelObject> spObject;

//
// Assume we have gotten some object in spObject that is iterable (e.g.: an object which represents a std::vector<SOMESTRUCT>)
//
ComPtr<IIterableConcept> spIterable;
ComPtr<IIndexableConcept> spIndexer;
if (SUCCEEDED(spObject->GetConcept(__uuidof(IIterableConcept), &spIterable, nullptr)) &&
    SUCCEEDED(spObject->GetConcept(__uuidof(IIndexableConcept), &spIndexable, nullptr)))
{
    ComPtr<IModelIterator> spIterator;

    //
    // Determine how many dimensions the default indexer is and allocate the requisite buffer.
    //
    ULONG64 dimensions;
    if (SUCCEEDED(spIterable->GetDefaultIndexDimensionality(spObject.Get(), &dimensions)) && dimensions > 0 &&
        SUCCEEDED(spIterable->GetIterator(spObject.Get(), &spIterator)))
    {
        std::unique_ptr<ComPtr<IModelObject>[]> spIndexers(new ComPtr<IModelObject>[dimensions]);

        //
        // We have an iterator.  Error codes have semantic meaning here.  E_BOUNDS indicates the end of iteration.  E_ABORT indicates that
        // the debugger host or application is trying to abort whatever operation is occurring.  Anything else indicates
        // some other error (e.g.: memory read failure) where the iterator MIGHT still produce values.
        //
        for(;;)
        {
            ComPtr<IModelObject> spContainedStruct;
            ComPtr<IKeyStore> spContainedMetadata;

            //
            // When we fetch the value from the iterator, it will pass back the default indices.
            //
            HRESULT hr = spIterable->GetNext(&spContainedStruct, dimensions, reinterpret_cast<IModelObject **>(spIndexers.get()), &spContainedMetadata);
            if (hr == E_BOUNDS || hr == E_ABORT)
            {
                break;
            }

            if (FAILED(hr))
            {
                //
                // Decide how to deal with failure to fetch an element.  Note that spContainedStruct *MAY* contain an error object
                // which has detailed information about why the failure occurred (e.g.: failure to read memory at address X).
                //
            }

            //
            // Use the indexer to get back to the same value.  We already have them, so there isn't much functional point to this.  It simply
            // highlights the interplay between iterator and indexer.
            //
            ComPtr<IModelObject> spIndexedStruct;
            ComPtr<IKeyStore> spIndexedMetadata;

            if (SUCCEEDED(spIndexer->GetAt(spObject.Get(), dimensions, reinterpret_cast<IModelObject **>(spIndexers.get()), &spIndexedStruct, &spIndexedMetadata)))
            {
                //
                // spContainedStruct and spIndexedStruct refer to the same object.  They may not have interface equality.
                // spContainedMetadata and spIndexedMetadata refer to the same metadata store with the same contents.  They may not have interface equality.
                //
            }
        }
    }
}

GetDimensionality

GetDimensionality 方法會傳回物件在 中編製索引的維度數目。 請注意,如果物件既可反覆運算又可編製索引,GetDefaultIndexDimensionality 的實作必須同意索引器擁有多少維度的 GetDimensionality 實作。

GetAt

GetAt 方法會從索引物件內擷取特定 N 維索引的值。 必須支援一個 N 維度的索引器,其中 N 是從 GetDimensionality 傳回的值。 請注意,物件可以在不同的領域中通過不同類型進行索引(例如:可透過序數和字串編製索引)。 如果索引超出範圍(或無法存取),方法會傳回失敗;不過,在這種情況下,輸出物件可能仍會設定為錯誤物件。

SetAt

SetAt 方法會嘗試從索引物件內的特定 N 維索引設定值。 必須支援從 GetDimensionality 傳回的值作為 N 的 N 維度索引器。 請注意,物件可能在不同領域中被不同類型編製索引(例如:可使用序數和字串編製索引)。 某些索引器是唯讀的。 在這種情況下,E_NOTIMPL會從 SetAt 方法的任何呼叫傳回。

慣用運行時間類型概念:IPreferredRuntimeTypeConcept

您可以查詢偵錯主機,以嘗試從符號資訊中找到的靜態類型判斷對象的實際運行時間類型。 此轉換可能以精確資訊為基礎(例如:C++ RTTI),或可能以強啟發法為基礎,例如物件內任何虛擬函式表的形狀。 不過,某些對象無法從靜態轉換成運行時間類型,因為它們不適合偵錯主機的啟發學習法(例如:它們沒有 RTTI 或虛擬函式數據表)。 在這種情況下,對象的數據模型可以選擇覆寫預設行為,並宣告它比偵錯主機更了解物件的「運行時間類型」。 這是透過 IPreferredRuntimeTypeConcept 介面的慣用運行時間類型概念和支援來完成。

IPreferredRuntimeTypeConcept 介面宣告如下:

DECLARE_INTERFACE_(IPreferredRuntimeTypeConcept, IUnknown)
{
    STDMETHOD(CastToPreferredRuntimeType)(_In_ IModelObject* contextObject, _COM_Errorptr_ IModelObject** object) PURE;
}

CastToPreferredRuntimeType

每當用戶端想要嘗試從靜態類型實例轉換成該實例的運行時間類型時,就會呼叫 CastToPreferredRuntimeType 方法。 如果所關注的對象透過其附加的父模型之一支援慣用的運行時間類型概念,則會呼叫這個方法來執行轉換。 這個方法可能會傳回原始物件(沒有轉換或無法分析)、傳回運行時間類型的新實例、因非語意原因而失敗(例如:記憶體不足),或傳回E_NOT_SET。 E_NOT_SET 錯誤碼是一個非常特殊的錯誤碼,會向數據模型指出實作不想覆寫預設行為,且數據模型應該恢復到偵錯主機所執行的任何分析(例如:RTTI 分析、虛擬函數表的形狀檢查等)。

動態提供者概念:IDynamicKeyProviderConcept 和 IDynamicConceptProviderConcept

雖然數據模型本身通常會處理物件的索引鍵和概念管理,但有時候該概念不理想。 特別是,當客戶想要建立數據模型與真正動態的其他事物(如 JavaScript)之間的橋樑時,接管數據模型中的關鍵和概念管理可能會非常有用。 核心數據模型是 IModelObject 的唯一實作,因此改以兩個概念的組合來完成:動態鍵提供者和動態概念提供者。 雖然通常會實作兩者或都不實作,但沒有這樣的要求。

如果兩者都已實作,則必須在動態概念提供者概念之前新增動態索引鍵提供者概念。 這兩個概念都是特殊的。 它們有效地在物件上翻轉開關,將其從「靜態管理」變更為「動態管理」。 只有在對象上沒有數據模型所管理的索引鍵/概念時,才能設定這些概念。 將這些概念新增至 對象之後,執行此動作是不可撤銷的。

IModelObject 作為動態概念提供者與非動態概念提供者之間的擴充性存在另外的語意差異。 這些概念旨在讓客戶端在數據模型與 JavaScript 等動態語言系統之間建立橋樑。 數據模型具有擴充性的概念,與 JavaScript 等系統基本不同,因為父模型樹狀結構,而不是 JavaScript 原型鏈結之類的線性鏈結。 為了讓這類系統有更好的關聯,作為動態概念提供者的 IModelObject 擁有一個單一的數據模型父系。 該單一數據模型父代是一般IModelObject,其父模型數目可以任意數目,如同數據模型一般。 向動態概念提供者申請新增或移除父系的任何要求將自動被重新導向至單一父系。 從外部人的視角來看,動態概念提供者似乎擁有一種正常的樹狀結構,這種結構包含父模型鏈。 動態概念提供者的概念的實現者是唯一知道中介單一父系的物件(位於核心數據模型之外)。 該單一父物件可以連結至動態語言系統,以提供橋接(例如:置於 JavaScript 原型鏈中)。

動態金鑰提供者概念的定義如下:

DECLARE_INTERFACE_(IDynamicKeyProviderConcept, IUnknown)
{
    STDMETHOD(GetKey)(_In_ IModelObject *contextObject, _In_ PCWSTR key, _COM_Outptr_opt_result_maybenull_ IModelObject** keyValue, _COM_Outptr_opt_result_maybenull_ IKeyStore** metadata, _Out_opt_ bool *hasKey) PURE;
    STDMETHOD(SetKey)(_In_ IModelObject *contextObject, _In_ PCWSTR key, _In_ IModelObject *keyValue, _In_ IKeyStore *metadata) PURE;
    STDMETHOD(EnumerateKeys)(_In_ IModelObject *contextObject, _COM_Outptr_ IKeyEnumerator **ppEnumerator) PURE;
}

動態概念提供者概念的定義如下:

DECLARE_INTERFACE_(IDynamicConceptProviderConcept, IUnknown)
{
    STDMETHOD(GetConcept)(_In_ IModelObject *contextObject, _In_ REFIID conceptId, _COM_Outptr_result_maybenull_ IUnknown **conceptInterface, _COM_Outptr_opt_result_maybenull_ IKeyStore **conceptMetadata, _Out_ bool *hasConcept) PURE;
    STDMETHOD(SetConcept)(_In_ IModelObject *contextObject, _In_ REFIID conceptId, _In_ IUnknown *conceptInterface, _In_opt_ IKeyStore *conceptMetadata) PURE;
    STDMETHOD(NotifyParent)(_In_ IModelObject *parentModel) PURE;
    STDMETHOD(NotifyParentChange)(_In_ IModelObject *parentModel) PURE;
    STDMETHOD(NotifyDestruct)() PURE;
}

IDynamicKeyProviderConcept 的 GetKey

動態密鑰提供者上的 GetKey 方法基本上是 IModelObject 上的 GetKey 方法覆寫。 動態金鑰提供者應該會傳回索引鍵的值,以及與該索引鍵相關聯的任何元數據。 如果索引鍵不存在(但未發生其他錯誤),則提供者必須在 hasKey 參數中傳回 false,並以 S_OK 表示成功。 此呼叫失敗被視為無法擷取密鑰,且會透過父模型鏈結明確停止搜尋密鑰。 在 hasKey 中傳回 false,成功將會繼續搜尋密鑰。 請注意,讓 GetKey 傳回 "Boxed 屬性存取子" 作為鍵是完全合法的。 這在語意上等同於 IModelObject 上的 GetKey 方法返回一個屬性存取器。

IDynamicKeyProviderConcept 的 SetKey

動態密鑰提供者上的 SetKey 方法實際上是 IModelObject 上 SetKey 方法的覆寫。 這會在動態提供者中設定鍵值。 實際上,這相當於在提供者上創建一個新的屬性。 請注意,不支援建立 expando 屬性等任何概念的提供者應該在此傳回E_NOTIMPL。

IDynamicKeyProviderConcept 的 EnumerateKeys

動態密鑰提供者上的 EnumerateKeys 方法實際上是 IModelObject 上 EnumerateKeys 方法的覆寫。 此會列出動態提供者中的所有鍵。 傳回的列舉值有數個限制必須遵守實作:

  • 它必須被視為對 EnumerateKeys 的呼叫,而不是對 EnumerateKeyValues 或 EnumerateKeyReferences 的呼叫。 它必須傳回索引鍵值,而不會解析任何基礎屬性存取子(如果提供者中有這類概念)。
  • 從單一動態金鑰提供者的觀點來看,將同名但實體不同的多個金鑰進行枚舉是非法的。 這可能會發生在通過父模型鏈接附加到的不同提供者上,但從單一提供者的角度來看,這是無法發生的。

IDynamicConceptProviderConcept 的 GetConcept

動態概念提供者上的 GetConcept 方法實際上是 IModelObject 上的 GetConcept 方法覆寫。 動態概念提供者必須傳回查詢概念的介面,若該查詢概念存在,並且存在與其相關的任何元數據。 如果在提供者上找不到此概念,則必須透過 hasConcept 參數返回 false 值來表示,並且要成功返回。 如果此方法失敗,就表示無法擷取概念,並會明確中止概念的搜尋。 當 hasConcept 傳回 false 且條件成功時,將透過父模型樹繼續搜尋概念。

IDynamicConceptProviderConcept 的 SetConcept

動態概念提供者上的 SetConcept 方法實際上是 IModelObject 上的 SetConcept 方法覆寫。 動態提供者會指派概念。 這可能會使物件可反覆運算、可編制索引、可轉換字串等...請注意,不允許在它上建立概念的提供者應該會在這裡傳回E_NOPTIMPL。

IDynamicConceptProviderConcept 的 NotifyParent

核心數據模型使用動態概念提供者的 NotifyParent 呼叫來通知動態提供者關於單一父模型,該單一父模型是為了將數據模型中的「多個父模型」範例橋接至更具動態性的語言而建立。 該單一父模型的任何操作會導致動態提供者收到進一步的通知。 請注意,此回呼會在指派動態概念提供者概念時立即進行。

IDynamicConceptProviderConcept 的 NotifyParentChange

動態概念提供者上的 NotifyParent 方法是由核心數據模型在靜態操作對象的單一父模型時所執行的回呼。 針對新增的任何指定父模型,這個方法會在新增父模型時第一次呼叫,並在移除父模型時呼叫第二次。

IDynamicConceptProviderConcept 的 NotifyDestruct

動態概念提供者的 NotifyDestruct 方法是由核心資料模型在對象解構開始時執行的回呼,那個對象是動態概念提供者。 它會為需要它的客戶提供額外的清除機會。

--

另請參閱

本主題是一系列的一部分,描述可從C++存取的介面、如何使用它們來建置以C++為基礎的調試程式延伸模組,以及如何從C++數據模型延伸模組使用其他數據模型建構(例如:JavaScript 或 NatVis)。

調試程序數據模型C++概觀

調試程序數據模型C++介面

調試程序數據模型C++物件

調試程序數據模型C++其他介面

調試程序數據模型C++概念

調試程序數據模型C++腳本