共用方式為


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

本主題描述調試程式 C++ 資料模型 中的概念。

數據模型中的概念

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

  • 索引鍵/值/元數據元組的字典。
  • 數據模型支援的一組概念(介面)。 概念是客戶端實作的介面(與數據模型相反),以提供一組指定的語意行為。 目前支援的一組概念列在這裡。
概念介面 描述
IDataModelConcept 概念是父模型。 如果此模型透過已註冊的類型簽章自動附加至原生類型,則每次具現化這類類型的新物件時,都會自動呼叫 InitializeObject 方法。
IStringDisplayableConcept 對象可以轉換成字串以供顯示之用。
IIterableConcept 對像是容器,可以進行一次查看。
IIndexableConcept 對像是容器,可以在一或多個維度中編製索引(透過隨機存取)。
IPreferredRuntimeTypeConcept 物件更瞭解衍生自的類型,而不是基礎類型系統能夠提供,而且想要處理其從靜態到運行時間類型的轉換。
IDynamicKeyProviderConcept 對像是索引鍵的動態提供者,希望從核心數據模型接管所有密鑰查詢。 此介面通常用來作為動態語言的網橋,例如 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 所產生的數位)。

如果反覆運算器成功向前移動,但讀取反覆運算專案的值時發生錯誤,方法可能會傳回錯誤 ,並將 錯誤物件填入 「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 維索引設定值。 N 維度的索引器,其中 N 是必須支援從 GetDimensionality 傳回的值。 請注意,物件可以透過不同類型在不同的網域中編製索引(例如:可透過序數和字串編製索引)。 某些索引器是唯讀的。 在這種情況下,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 分析、虛擬函式數據表的形狀檢查。 etc...)

動態提供者概念:IDynamicKeyProviderConcept 和 IDynamicConceptProviderConcept

雖然數據模型本身通常會處理物件的索引鍵和概念管理,但有時候該概念不理想。 特別是,當用戶端想要在數據模型與真正動態的其他項目之間建立橋樑時,從數據模型中的實作接管重要和概念管理可能會很實用。 核心數據模型是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++ 腳本