Conceitos do Modelo de Dados do Depurador C++
Este tópico descreve conceitos no Modelo de Dados do Depurador C++.
Conceitos no Modelo de Dados
Os objetos sintéticos no modelo de dados são duas coisas:
- Um dicionário de tuplas de chave/valor/metadados.
- Um conjunto de conceitos (interfaces) compatíveis com o modelo de dados. Conceitos são interfaces que um cliente (e não o modelo de dados) implementa para fornecer um conjunto especificado de comportamentos semânticos. O conjunto de conceitos atualmente aceito está listado aqui.
Interface de conceito | Descrição |
---|---|
IDataModelConcept | O conceito é um modelo pai. Se esse modelo for anexado automaticamente a um tipo nativo por meio de uma assinatura de tipo registrado, o método InitializeObject será automaticamente chamado sempre que um novo objeto desse tipo for instanciado. |
IStringDisplayableConcept | O objeto pode ser convertido em uma cadeia de caracteres para fins de exibição. |
IIterableConcept | O objeto é um contêiner e pode ser iterado. |
IIndexableConcept | O objeto é um contêiner e pode ser indexado (acessado via acesso aleatório) em uma ou mais dimensões. |
IPreferredRuntimeTypeConcept | O objeto entende mais sobre os tipos derivados dele do que o sistema de tipos subjacente é capaz de fornecer e gostaria de lidar com suas próprias conversões do tipo estático no tipo de runtime. |
IDynamicKeyProviderConcept | O objeto é um provedor dinâmico de chaves e deseja assumir todas as consultas de chave do modelo de dados principal. Essa interface geralmente é usada como uma ponte para linguagens dinâmicas, como JavaScript. |
IDynamicConceptProviderConcept | O objeto é um provedor dinâmico de conceitos e deseja assumir todas as consultas de conceito do modelo de dados principal. Essa interface geralmente é usada como uma ponte para linguagens dinâmicas, como JavaScript. |
O conceito do modelo de dados: IDataModelConcept
Qualquer objeto de modelo anexado a outro como um modelo pai deve oferecer suporte direto ao conceito de modelo de dados. O conceito de modelo de dados requer o suporte de uma interface, IDataModelConcept definido a seguir.
DECLARE_INTERFACE_(IDataModelConcept, IUnknown)
{
STDMETHOD(InitializeObject)(_In_ IModelObject* modelObject, _In_opt_ IDebugHostTypeSignature* matchingTypeSignature, _In_opt_ IDebugHostSymbolEnumerator* wildcardMatches) PURE;
STDMETHOD(GetName)(_Out_ BSTR* modelName) PURE;
}
Um modelo de dados pode ser registrado como o visualizador canônico ou como uma extensão de um determinado tipo nativo por meio dos métodos RegisterModelForTypeSignature ou RegisterExtensionForTypeSignature do gerenciador de modelo de dados. Quando um modelo é registrado por meio de um desses métodos, o modelo de dados é anexado automaticamente como um modelo pai a qualquer objeto nativo cujo tipo corresponda à assinatura passada no registro. No ponto em que esse anexo é criado automaticamente, o método InitializeObject é chamado no modelo de dados. São passados o objeto de instância, a assinatura de tipo que causou o anexo e um enumerador que produz as instâncias de tipo (em ordem linear) que corresponderam a quaisquer curingas na assinatura de tipo. A implementação do modelo de dados pode usar essa chamada de método para inicializar os caches necessários.
Se um determinado modelo de dados for registrado com um nome padrão por meio do método RegisterNamedModel, a interface IDataModelConcept do modelo de dados registrado deverá retornar esse nome desse método. É perfeitamente legítimo que um modelo seja registrado com vários nomes (o padrão ou o melhor deve ser retornado aqui). Um modelo pode ser completamente sem nome (desde que não esteja registrado com um nome). Nessas circunstâncias, o método GetName deve retornar E_NOTIMPL.
O conceito de cadeia de caracteres: IStringDisplayableConcept
Um objeto que deseja fornecer uma conversão de cadeia de caracteres para fins de exibição pode implementar o conceito de cadeia de caracteres que pode ser exibida por meio da implementação da interface IStringDisplayableConcept. A interface é definida assim:
DECLARE_INTERFACE_(IStringDisplayableConcept, IUnknown)
{
STDMETHOD(ToDisplayString)(_In_ IModelObject* contextObject, _In_opt_ IKeyStore* metadata, _Out_ BSTR* displayString) PURE;
}
O método ToDisplayString é chamado sempre que um cliente deseja converter um objeto em uma cadeia de caracteres para exibição (para o console, na interface do usuário etc.). Essa conversão de cadeia de caracteres não deve ser usada para a base de manipulação programática adicional. A conversão de cadeia de caracteres em si pode ser profundamente influenciada pelos metadados passados para a chamada. Uma conversão de cadeia de caracteres deve fazer todas as tentativas para honrar as chaves PreferredRadix e PreferredFormat.
O conceito iterável: IIterableConcept e IModelIterator
Um objeto que é um contêiner de outros objetos e quer expressar a capacidade de iterar sobre esses objetos contidos pode dar suporte ao conceito iterável por uma implementação das interfaces IIterableConcept e IModelIterator. Há uma relação muito importante entre o suporte do conceito iterável e o suporte do conceito indexável. Um objeto que dá suporte ao acesso aleatório aos objetos contidos pode dar suporte ao conceito indexável, além do conceito iterável. Nesse caso, os elementos iterados também devem produzir um índice padrão que, quando passado para o conceito indexável, refere-se ao mesmo objeto. Se essa invariante não for satisfeita, o comportamento ficará indefinido no host de depuração.
O IIterableConcept é definido assim:
DECLARE_INTERFACE_(IIterableConcept, IUnknown)
{
STDMETHOD(GetDefaultIndexDimensionality)(_In_ IModelObject* contextObject, _Out_ ULONG64* dimensionality) PURE;
STDMETHOD(GetIterator)(_In_ IModelObject* contextObject, _Out_ IModelIterator** iterator) PURE;
}
O conceito IModelIterator é definido assim:
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;
}
GetDefaultIndexDimensionality do IIterableConcept
O método GetDefaultIndexDimensionality retorna o número de dimensões para o índice padrão. Se um objeto não for indexável, esse método deverá retornar 0 e ter êxito (S_OK). Qualquer objeto que retorna um valor diferente de zero desse método está declarando suporte a um contrato de protocolo que declara:
- O objeto dá suporte ao conceito indexável por meio do suporte a IIndexableConcept
- O método GetNext do IModelIterator retornado do método GetIterator do conceito iterável retornará um índice padrão exclusivo para cada elemento produzido. Tal índice terá o número de dimensões conforme indicado aqui.
- Quando os índices retornados do método GetNext do IModelIterator são passados para o método GetAt no conceito indexável (IIndexableConcept), é feita referência ao mesmo objeto que GetNext produziu. O mesmo valor é retornado.
GetIterator do IIterableConcept
O método GetIterator no conceito iterável retorna uma interface iteradora que pode ser usada para iterar o objeto. O iterador retornado deve lembrar o objeto de contexto que foi passado para o método GetIterator. Ele não será passado para métodos no próprio iterador.
Reset do IModelIterator
O método Reset em um iterador retornado do conceito iterável irá restaurar a posição do iterador para onde estava quando o iterador foi criado pela primeira vez (antes do primeiro elemento). Embora seja altamente recomendável que o iterador dê suporte ao método Reset, isso não é obrigatório. Um iterador pode ser o equivalente a um iterador de entrada C++ e permitir uma única passagem de iteração direta. Nesse caso, o método Reset pode falhar com E_NOTIMPL.
GetNext do IModelIterator
O método GetNext faz o iterador avançar e busca o próximo elemento iterado. Se o objeto for indexável além de iterável e isso for indicado pelo argumento GetDefaultIndexDimensionality retornando um valor diferente de zero, esse método poderá retornar os índices padrão para voltar ao valor produzido do indexador. Um autor de chamada pode optar por passar 0/nullptr e não recuperar nenhum índice. É considerado ilegal que o autor da chamada solicite índices parciais (por exemplo: menor que o número produzido por GetDefaultIndexDimensionality).
Se o iterador avançou com êxito, mas houve um erro na leitura do valor do elemento iterado, o método poderá retornar um erro E preencher "object" com um objeto de erro. No final da iteração dos elementos contidos, o iterador retornará E_BOUNDS do método GetNext. Todas as chamadas subsequentes (a menos que tenha havido uma chamada Reset de intervenção) também retornará E_BOUNDS.
O conceito indexável: IIndexableConcept
Um objeto que deseja fornecer acesso aleatório a um conjunto de conteúdos pode dar suporte ao conceito indexável por meio do suporte da interface IIndexableConcept. A maioria dos objetos que são indexáveis também serão iteráveis por meio do suporte do conceito iterável. No entanto, isso não é obrigatório. Se houver suporte, haverá uma relação importante entre o iterador e o indexador. O iterador deve dar suporte a GetDefaultIndexDimensionality, retornar um valor diferente de zero desse método e dar suporte ao contrato documentado lá. A interface do conceito do indexador é definida assim:
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;
}
Um exemplo de uso do indexador (e sua interação com o iterador) é mostrado abaixo. Este exemplo itera o conteúdo de um contêiner indexável e usa o indexador para retornar ao valor que acabou de ser retornado. Embora essa operação seja funcionalmente inútil como escrita, ela demonstra como essas interfaces interagem. O exemplo abaixo não lida com falhas de alocação de memória. Ele pressupõe um lançamento novo (que pode ser uma suposição ruim, dependendo do ambiente em que o código existe; os métodos COM do modelo de dados não podem ter escape de exceções 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.
//
}
}
}
}
O método GetDimensionality retorna o número de dimensões em que o objeto está indexado. Se o objeto for iterável e indexável, a implementação de GetDefaultIndexDimensionality deverá concordar com a implementação de GetDimensionality quanto ao número de dimensões que o indexador tem.
O método GetAt recupera o valor em um índice N-dimensional específico de dentro do objeto indexado. Deve haver suporte para um indexador de N dimensões, em que N é o valor retornado de GetDimensionality. Um objeto pode ser indexável em diferentes domínios por diferentes tipos (por exemplo: indexável por meio de ordinais e cadeias de caracteres). Se o índice estiver fora do intervalo (ou não puder ser acessado), o método retornará uma falha. No entanto, nesses casos, o objeto de saída ainda poderá ser definido como um objeto de erro.
O método SetAt tenta definir o valor em um determinado índice N-dimensional de dentro do objeto indexado. Deve haver suporte para um indexador de N dimensões, em que N é o valor retornado de GetDimensionality. Um objeto pode ser indexável em diferentes domínios por diferentes tipos (por exemplo: indexável por meio de ordinais e cadeias de caracteres). Alguns indexadores são somente leitura. Nesses casos, E_NOTIMPL será retornado de qualquer chamada para o método SetAt.
O conceito de tipo de runtime preferido: IPreferredRuntimeTypeConcept
Um host de depuração pode ser consultado para tentar determinar o tipo de runtime real de um objeto a partir de um tipo estático encontrado em informações simbólicas. Essa conversão pode ser baseada em informações completamente precisas (por exemplo: RTTI C++) ou pode ser baseada em uma heurística forte, como a forma de qualquer tabela de função virtual dentro do objeto. Alguns objetos, no entanto, não podem ser convertidos de um tipo estático para um tipo de runtime porque não se encaixam na heurística do host de depuração (por exemplo: eles não têm RTTI ou tabelas de função virtual). Nesses casos, um modelo de dados de um objeto pode optar por substituir o comportamento padrão e declarar que sabe mais sobre o "tipo de runtime" de um objeto do que o host de depuração é capaz de entender. Isso é feito por meio do conceito de tipo de runtime preferencial e do suporte da interface IPreferredRuntimeTypeConcept.
A interface IPreferredRuntimeTypeConcept é declarada assim:
DECLARE_INTERFACE_(IPreferredRuntimeTypeConcept, IUnknown)
{
STDMETHOD(CastToPreferredRuntimeType)(_In_ IModelObject* contextObject, _COM_Errorptr_ IModelObject** object) PURE;
}
O método CastToPreferredRuntimeType é chamado sempre que um cliente quer tentar converter de uma instância de tipo estático para o tipo de runtime dessa instância. Se o objeto em questão der suporte (por meio de um de seus modelos pai anexados) ao conceito de tipo de runtime preferencial, esse método será chamado para executar a conversão. Esse método pode retornar o objeto original (não há conversão ou não é possível analisar), retornar uma nova instância do tipo de runtime, falhar por motivos não semânticos (por exemplo: falta de memória) ou retornar E_NOT_SET. O E_NOT_SET é um código de erro muito especial que indica ao modelo de dados que a implementação não quer substituir o comportamento padrão e que o modelo de dados deve retornar à análise executada pelo host de depuração (por exemplo: análise RTTI, exame da forma das tabelas de função virtual etc.)
Os conceitos de provedor dinâmico: IDynamicKeyProviderConcept e IDynamicConceptProviderConcept
Embora o próprio modelo de dados normalmente lide com o gerenciamento de chaves e conceitos de objetos, há momentos em que essa noção não é ideal. Em particular, quando um cliente deseja criar uma ponte entre o modelo de dados e outra coisa que seja verdadeiramente dinâmica (por exemplo: JavaScript), pode ser valioso assumir o gerenciamento de chaves e conceitos a partir da implementação no modelo de dados. Como o modelo de dados principal é a única implementação do IModelObject, isso é feito por meio de uma combinação de dois conceitos: o conceito de provedor de chave dinâmica e o conceito de provedor de conceito dinâmico. Embora seja típico implementar ambos ou nenhum dos dois, não há exigência para tal.
Se ambos forem implementados, o conceito de provedor de chave dinâmica deverá ser adicionado antes do conceito de provedor de conceito dinâmico. Ambos os conceitos são especiais. Eles "viram a chave" no objeto, alterando-o de "gerenciado estaticamente" para "gerenciado dinamicamente". Esses conceitos só poderão ser definidos se não houver chaves/conceitos gerenciados pelo modelo de dados no objeto. Quando esses conceitos forem adicionados a um objeto, essa ação será irrevogável.
Há uma diferença semântica adicional em torno da extensibilidade entre um IModelObject que é um provedor de conceito dinâmico e um que não é. Esses conceitos destinam-se a permitir que os clientes criem pontes entre o modelo de dados e os sistemas de linguagem dinâmica, como JavaScript. O modelo de dados tem um conceito de extensibilidade que difere um pouco fundamentalmente de sistemas como JavaScript em que há uma árvore de modelos pai, em vez de uma cadeia linear como a cadeia de protótipos JavaScript. Para permitir um melhor relacionamento com esses sistemas, um IModelObject que é um provedor de conceito dinâmico tem um único pai de modelo de dados. Esse pai de modelo de dados único é um IModelObject normal que pode ter um número arbitrário de modelos pai, como é típico para o modelo de dados. Todas as solicitações ao provedor de conceito dinâmico para adicionar ou remover pais são automaticamente redirecionadas para o único pai. Da perspectiva de um objeto externo, parece que o provedor de conceito dinâmico tem uma cadeia de modelos pai em estilo de árvore normal. O implementador do conceito de provedor de conceito dinâmico é o único objeto (fora do modelo de dados principal) que reconhece o pai único intermediário. Esse pai único pode ser vinculado ao sistema de linguagem dinâmica para fornecer uma ponte (por exemplo: colocado na cadeia de protótipos JavaScript).
O conceito de provedor de chave dinâmica é definido assim:
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;
}
O conceito de provedor de conceito dinâmico é definido assim:
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;
}
GetKey do IDynamicKeyProviderConcept
O método GetKey em um provedor de chave dinâmica é em grande parte uma substituição do método GetKey no IModelObject. Espera-se que o provedor de chave dinâmica retorne o valor da chave e todos os metadados associados a essa chave. Caso a chave não esteja presente (mas nenhum outro erro ocorra), o provedor deverá retornar false no parâmetro hasKey e ter êxito com S_OK. A falha nessa chamada é considerada uma falha na busca de uma chave e interromperá explicitamente a pesquisa pela chave por meio da cadeia do modelo pai. Retornar false em hasKey e success continuará a busca pela chave. É perfeitamente legal que GetKey retorne um acessador de propriedade boxed como a chave. Isso seria semanticamente idêntico ao método GetKey no IModelObject retornando um acessador de propriedade.
SetKey do IDynamicKeyProviderConcept
O método SetKey em um provedor de chave dinâmica é uma substituição do método SetKey no IModelObject. Isso define uma chave no provedor dinâmico. É a criação de uma nova propriedade no provedor. Um provedor que não dá suporte a nenhuma noção de algo como a criação de propriedades expando deve retornar E_NOTIMPL aqui.
EnumerateKeys do IDynamicKeyProviderConcept
O método EnumerateKeys em um provedor de chave dinâmica é uma substituição do método EnumerateKeys no IModelObject. Isso enumera todas as chaves no provedor dinâmico. O enumerador retornado tem várias restrições que devem ser respeitadas pela implementação:
- Ele deve se comportar como uma chamada para EnumerateKeys, e não EnumerateKeyValues ou EnumerateKeyReferences. Ele deve retornar os valores de chave não resolvendo nenhum acessador de propriedade subjacente (se esse conceito existir no provedor).
- Da perspectiva de um único provedor de chave dinâmica, é ilegal enumerar várias chaves do mesmo nome que são chaves fisicamente distintas. Isso pode acontecer em diferentes provedores que estão conectados por meio da cadeia de modelo pai, mas não pode acontecer da perspectiva de um único provedor.
GetConcept do IDynamicConceptProviderConcept
O método GetConcept em um provedor de conceito dinâmico é uma substituição do método GetConcept no IModelObject. O provedor de conceito dinâmico deve retornar uma interface para o conceito consultado, se ele existir, bem como todos os metadados associados a esse conceito. Se o conceito não existir no provedor, isso deverá ser indicado por meio de um valor falso retornado no argumento hasConcept e um retorno bem-sucedido. A falha desse método é uma falha em buscar o conceito, e ela interromperá explicitamente a busca pelo conceito. Retornar false para hasConcept e um código bem-sucedido continuará a busca pelo conceito por meio da árvore de modelos pai.
SetConcept do IDynamicConceptProviderConcept
O método SetConcept em um provedor de conceito dinâmico é uma substituição do método SetConcept no IModelObject. O provedor dinâmico atribuirá o conceito. Isso pode tornar o objeto iterável, indexável, conversível em cadeia de caracteres etc... Um provedor que não permite a criação de conceitos sobre ele deve retornar E_NOPTIMPL aqui.
NotifyParent do IDynamicConceptProviderConcept
A chamada NotifyParent em um provedor de conceito dinâmico é usada pelo modelo de dados principal para informar o provedor dinâmico do modelo pai único, que é criado para permitir a ponte entre o paradigma de "vários modelos pai" do modelo de dados para linguagens mais dinâmicas. Qualquer manipulação desse modelo pai único causará notificações adicionais para o provedor dinâmico. Esse retorno de chamada é feito imediatamente após a atribuição do conceito de provedor de conceito dinâmico.
NotifyParentChange do IDynamicConceptProviderConcept
O método NotifyParent em um provedor de conceito dinâmico é um retorno de chamada feito pelo modelo de dados principal quando é feita uma manipulação estática do modelo pai único do objeto. Para qualquer modelo pai adicionado, esse método será chamado uma primeira vez quando o modelo pai for adicionado e uma segunda vez se/quando o modelo pai for removido.
NotifyDestruct do IDynamicConceptProviderConcept
O método NotifyDestruct em um provedor de conceito dinâmico é um retorno de chamada feito pelo modelo de dados principal no início da destruição do objeto, que é um provedor de conceito dinâmico. Ele oferece oportunidades adicionais de limpeza para os clientes que precisam disso.
--
Consulte também
Este tópico faz parte de uma série que descreve as interfaces acessíveis a partir de C++, como usá-las para criar uma extensão de depurador baseada em C++ e como fazer uso de outras construções de modelo de dados (por exemplo: JavaScript ou NatVis) a partir de uma extensão de modelo de dados C++.
Visão geral do Modelo de Dados do Depurador C++
Interfaces do Modelo de Dados do Depurador C++
Objetos do Modelo de Dados do Depurador C++
Interfaces adicionais do Modelo de Dados do Depurador C++