调试器数据模型 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

每当客户端希望将对象转换为字符串以显示 (到控制台、UI 等时,将调用 ToDisplayString 方法...) 。此类字符串转换不应用作其他编程操作的基础。 字符串转换本身可能会受到传递给调用的元数据的深刻影响。 字符串转换应每次尝试都遵循 PreferredRadix 和 PreferredFormat 键。

可迭代概念:IIterableConcept 和 IModelIterator

作为其他对象的容器并且希望表达循环访问包含对象的能力的对象可以通过实现 IIterableConcept 和 IModelIterator 接口来支持可迭代概念。 支持可迭代概念与支持可索引概念之间存在非常重要的关系。 支持随机访问包含对象的对象除了支持可迭代概念外,还可以支持可索引的概念。 在这种情况下,迭代元素还必须生成默认索引,当传递给可索引概念时,该索引将引用同一对象。 无法满足此固定项将导致调试主机中出现未定义的行为。

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 indicies.
            //
            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

虽然数据模型本身通常处理对象的键和概念管理,但有时这种概念并不理想。 特别是,当客户端希望在数据模型和其他真正动态 ((例如 JavaScript) )之间创建桥梁时,从数据模型中的实现中接管关键和概念管理可能很有价值。 由于核心数据模型是 IModelObject 的唯一实现,而是通过以下两个概念的组合实现:动态密钥提供程序概念和动态概念提供程序概念。 虽然通常同时实现或两者都不实现,但不需要这样做。

如果两者都实现了,则必须在动态概念提供程序概念之前添加动态密钥提供程序概念。 这两个概念都很特殊。 它们有效地翻转对象上的开关,将其从“静态管理”更改为“动态管理”。 仅当对象上没有数据模型管理的键/概念时,才能设置这些概念。 将这些概念添加到 对象后,执行此操作的操作是不可撤销的。

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 将装箱属性访问器作为键返回是完全合法的。 这在语义上与 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 值和成功的返回来指示该概念。 此方法失败是无法提取概念,将显式停止对概念的搜索。 返回 false 的 hasConcept 和成功的代码将继续通过父模型树搜索概念。

IDynamicConceptProviderConcept 的 SetConcept

动态概念提供程序上的 SetConcept 方法实际上是 IModelObject 上 SetConcept 方法的替代。 动态提供程序将分配概念。 这可能会使对象可迭代、可索引、字符串可转换,等等...请注意,不允许对其创建概念的提供程序应在此处返回E_NOPTIMPL。

IDynamicConceptProviderConcept 的 NotifyParent

核心数据模型使用动态概念提供程序上的 NotifyParent 调用来通知动态提供程序单一父模型,该模型是为允许将数据模型的“多父模型”范例桥接到更动态的语言而创建的。 对该单父模型的任何操作都会导致向动态提供程序发送进一步的通知。 请注意,此回调是在分配动态概念提供程序概念后立即进行的。

IDynamicConceptProviderConcept 的 NotifyParentChange

动态概念提供程序上的 NotifyParent 方法是在对对象的单一父模型进行静态操作时由核心数据模型进行的回调。 对于添加的任何给定父模型,首次添加上述父模型时将调用此方法,如果/删除该父模型,则第二次调用此方法。

IDynamicConceptProviderConcept 的 NotifyDestruct

动态概念提供程序上的 NotifyDestruct 方法是核心数据模型在销毁对象(动态概念提供程序)开始时进行的回调。 它为需要它的客户提供了额外的清理机会。

--

另请参阅

本主题是一个系列的一部分,该系列介绍可从 C++ 访问的接口、如何使用接口生成基于 C++ 的调试器扩展,以及如何利用其他数据模型构造 (例如:JavaScript 或 NatVis) 从 C++ 数据模型扩展。

调试器数据模型 C++ 概述

调试器数据模型 C++ 接口

调试器数据模型 C++ 对象

调试器数据模型 C++ 的其他接口

调试器数据模型 C++ 的概念

调试器数据模型 C++ 脚本