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

本主题介绍调试器C++数据模型的概念。

数据模型中的概念

数据模型中的合成对象实际上是两件事:

  • 键/值/元数据元组的字典。
  • 数据模型支持一组概念(接口)。 概念是客户端(而不是数据模型)实现的接口,以提供一组指定的语义行为。 此处列出了当前支持的概念集。
概念接口 DESCRIPTION
IDataModelConcept 概念是父模型。 如果此模型通过注册的类型签名自动附加到原生类型,那么在每次实例化该类型的新对象时,都会自动调用 InitializeObject 方法。
IString显示概念 对象可以转换为字符串以供显示。
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 接口来支持可迭代的概念。 支持可迭代的概念和支持可索引概念之间存在非常重要的关系。 支持随机访问包含对象的对象除了可迭代的概念外,还可以支持可索引的概念。 在这种情况下,迭代元素还必须生成默认索引,当传递给可索引的概念时,该索引引用同一对象。 无法满足此固定项将导致调试主机中未定义的行为。

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 方法,但这不是必需的。 迭代器可以是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 维索引处设置值。 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 for hasConcept,并且成功的代码将继续通过父模型树搜索概念。

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++脚本