Debugger-Datenmodell C++ Objekte
Dieses Thema beschreibt, wie Sie Debugger-Datenmodell C++ Objekte verwenden und wie Sie die Funktionalitäten des Debuggers erweitern können.
Das Core-Debugger-Objektmodell
Eines der grundlegendsten und zugleich leistungsfähigsten Dinge am Datenmodell ist, dass es die Definition eines Objekts und die Interaktion mit einem Objekt standardisiert. Die Schnittstelle IModelObject kapselt die Definition eines Objekts – unabhängig davon, ob es sich bei diesem Objekt um eine Ganzzahl, eine Fließkommazahl, eine Zeichenfolge, einen komplexen Typ im Zieladressraum des Debuggers oder um ein Debugger-Konzept wie die Definition eines Prozesses oder eines Moduls handelt.
Es gibt verschiedene Dinge, die in einem IModelObject enthalten (oder darin gespeichert) sein können:
Intrinsische Werte – Ein IModelObject kann ein Container für eine Reihe von Grundtypen sein: 8, 16, 32 oder 64 Bit Ganzzahlen mit oder ohne Vorzeichen, boolesche Werte, Zeichenfolgen, Fehler oder empty.
Native Objekte – Ein IModelObject kann einen komplexen Typ (wie vom Typsystem des Debuggers definiert) innerhalb des Adressraums des Ziels des Debuggers darstellen.
Synthetische Objekte – Ein IModelObject kann ein dynamisches Objekt sein – ein Wörterbuch, wenn Sie so wollen: eine Sammlung von Schlüssel/Wert/Metadaten-Tupeln und eine Reihe von Konzepten, die Verhaltensweisen definieren, die nicht einfach durch Schlüssel/Wert-Paare dargestellt werden.
Eigenschaften – Ein IModelObject kann eine Eigenschaft darstellen: Etwas, dessen Wert durch einen Methodenaufruf abgerufen oder geändert werden kann. Eine Eigenschaft innerhalb eines IModelObject ist praktisch eine IModelPropertyAccessor-Schnittstelle, die in ein IModelObject eingebettet ist.
Methoden – Ein IModelObject kann eine Methode darstellen: Etwas, das Sie mit einer Reihe von Argumenten aufrufen und einen Rückgabewert erhalten können. Eine Methode innerhalb eines IModelObject ist praktisch eine IModelMethod-Schnittstelle, die in ein IModelObject eingebettet ist.
Erweiterbarkeit innerhalb des Objektmodells
Ein IModelObject ist kein isoliertes Objekt. Jedes Objekt repräsentiert nicht nur einen der oben genannten Objekttypen, sondern umfasst auch eine Kette von übergeordneten Datenmodellen. Diese Kette verhält sich ähnlich wie eine JavaScript-Prototypenkette. Anstelle einer linearen Kette von Prototypen wie bei JavaScript definiert jedes Datenmodellobjekt eine lineare Kette von übergeordneten Modellen. Jedes dieser übergeordneten Modelle hat wiederum eine weitere lineare Kette mit einer eigenen Reihe von übergeordneten Modellen. Im Wesentlichen ist jedes Objekt eine Aggregation der Funktionalitäten (Eigenschaften usw.) sowohl von sich selbst als auch von jedem Objekt in diesem Baum. Wenn eine bestimmte Eigenschaft abgerufen wird und das abgefragte Objekt diese Eigenschaft nicht unterstützt, wird die Abfrage in linearer Reihenfolge nacheinander an jedes übergeordnete Objekt weitergeleitet. Dadurch wird ein Verhalten erstellt, bei dem die Suche nach einer Eigenschaft durch eine Tiefensuche im Aggregationsbaum gelöst wird.
Die Erweiterbarkeit dieses Objektmodells ist sehr einfach, da jedes Objekt eine Aggregation seiner selbst und des Baums der übergeordneten Modelle ist. Eine Erweiterung kann sich selbst in die Liste der übergeordneten Modelle eines anderen Objekts einfügen. Auf diese Weise wird das Objekt erweitert. Auf diese Weise ist es möglich, Funktionalitäten zu allem hinzuzufügen: zu einer bestimmten Instanz eines Objekts oder Wertes, zu einem nativen Typ, zum Konzept des Debuggers, bei dem es sich um einen Prozess oder Thread handelt, oder sogar zur Definition des Begriffs „alle iterierbaren Objekte“.
Kontext, Kontext und Kontext: Der this-Zeiger, der Adressraum und die privaten Daten der Implementierung
Es gibt drei Begriffe von Kontext, die Sie im Zusammenhang mit dem Objektmodell verstehen müssen.
Kontext: Der this-Zeiger
Da eine bestimmte Eigenschaft oder Methode auf jeder Ebene des Datenmodellbaums implementiert werden kann, muss die Implementierung der Methode oder Eigenschaft in der Lage sein, auf das ursprüngliche Objekt zuzugreifen (was Sie in C++ als this-Zeiger oder in JavaScript als this-Objekt bezeichnen könnten). Dieses Instanz-Objekt wird an eine Vielzahl von Methoden als erstes Argument übergeben, das in den beschriebenen Methoden Kontext genannt wird.
Kontext: Der Adressraum
Es ist wichtig zu beachten, dass im Gegensatz zu früheren Erweiterungsmodellen, bei denen context (das Ziel, der Prozess, der Thread, den Sie betrachten) ein UI-Konzept ist, bei dem sich alle APIs auf den aktuellen UI-Status beziehen, Datenmodellschnittstellen diesen Kontext in der Regel entweder explizit oder implizit als IDebugHostContext-Schnittstelle übernehmen. Jedes IModelObject innerhalb des Datenmodells trägt diese Art von Kontextinformationen mit sich und kann diesen Kontext an Objekte weitergeben, die es zurückgibt. Das bedeutet, wenn Sie einen nativen Wert oder einen Schlüsselwert aus einem IModelObject auslesen, liest es aus dem Ziel und dem Prozess aus, von dem das Objekt ursprünglich erworben wurde.
Es gibt einen expliziten konstanten Wert, USE_CURRENT_HOST_CONTEXT, der an Methoden übergeben werden kann, die ein IDebugHostContext-Argument entgegennehmen. Dieser Wert zeigt an, dass der Kontext tatsächlich der aktuelle UI-Status des Debuggers sein sollte. Diese Definition muss jedoch explizit sein.
Kontext: Private Daten der Implementierung
Denken Sie daran, dass jedes Objekt im Datenmodell eigentlich eine Aggregation der Objektinstanz und des Baums übergeordneter Modelle ist, die angehängt sind. Jedes dieser übergeordneten Modelle (die in den Ketten vieler verschiedener Objekte verknüpft sein können) kann jedem Instanz-Objekt private Implementierungsdaten zuordnen. Jedes IModelObject, das konzeptionell erstellt wird, verfügt über eine Hash-Tabelle, die von einem bestimmten übergeordneten Modell auf private Instanzdaten zugreift, die durch eine IUnknown-Schnittstelle definiert sind. Dies bietet einem übergeordneten Modell die Möglichkeit, Informationen über jede Instanz zwischenzuspeichern oder anderweitig beliebige Daten zuzuordnen.
Der Zugriff auf diese Art von Kontext erfolgt über die Methoden GetContextForDataModel und SetContextForDataModel von IModelObject.
Die Core-Debugger-Objektschnittstelle: IModelObject
Die IModelObject-Schnittstelle ist wie folgt definiert:
DECLARE_INTERFACE_(IModelObject, IUnknown)
{
STDMETHOD(QueryInterface)(_In_ REFIID iid, _COM_Outptr_ PVOID* iface);
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)() PURE;
STDMETHOD(GetContext)(_COM_Outptr_result_maybenull_ IDebugHostContext** context) PURE;
STDMETHOD(GetKind)(_Out_ ModelObjectKind *kind) PURE;
STDMETHOD(GetIntrinsicValue)(_Out_ VARIANT* intrinsicData);
STDMETHOD(GetIntrinsicValueAs)(_In_ VARTYPE vt, _Out_ VARIANT* intrinsicData) PURE;
STDMETHOD(GetKeyValue)(_In_ PCWSTR key, _COM_Errorptr_opt_ IModelObject** object, _COM_Outptr_opt_result_maybenull_ IKeyStore** metadata) PURE;
STDMETHOD(SetKeyValue)(_In_ PCWSTR key, _In_opt_ IModelObject* object) PURE;
STDMETHOD(EnumerateKeyValues)(_COM_Outptr_ IKeyEnumerator** enumerator) PURE;
STDMETHOD(GetRawValue)(_In_ SymbolKind kind, _In_ PCWSTR name, _In_ ULONG searchFlags, _COM_Errorptr_ IModelObject** object) PURE;
STDMETHOD(EnumerateRawValues)(_In_ SymbolKind kind, _In_ ULONG searchFlags, _COM_Outptr_ IRawEnumerator** enumerator) PURE;
STDMETHOD(Dereference)(_COM_Errorptr_ IModelObject** object) PURE;
STDMETHOD(TryCastToRuntimeType)(_COM_Errorptr_ IModelObject** runtimeTypedObject) PURE;
STDMETHOD(GetConcept)(_In_ REFIID conceptId, _COM_Outptr_ IUnknown** conceptInterface, _COM_Outptr_opt_result_maybenull_ IKeyStore** conceptMetadata) PURE;
STDMETHOD(GetLocation)(_Out_ Location* location) PURE;
STDMETHOD(GetTypeInfo)(_Out_ IDebugHostType** type) PURE;
STDMETHOD(GetTargetInfo)(_Out_ Location* location, _Out_ IDebugHostType** type) PURE;
STDMETHOD(GetNumberOfParentModels)(_Out_ ULONG64* numModels) PURE;
STDMETHOD(GetParentModel)(_In_ ULONG64 i, _COM_Outptr_ IModelObject **model, _COM_Outptr_result_maybenull_ IModelObject **contextObject) PURE;
STDMETHOD(AddParentModel)(_In_ IModelObject* model, _In_opt_ IModelObject* contextObject, _In_ bool override) PURE;
STDMETHOD(RemoveParentModel)(_In_ IModelObject* model) PURE;
STDMETHOD(GetKey)(_In_ PCWSTR key, _COM_Errorptr_opt_ IModelObject** object, _COM_Outptr_opt_result_maybenull_ IKeyStore** metadata) PURE;
STDMETHOD(GetKeyReference)(_In_ PCWSTR key, _COM_Errorptr_opt_ IModelObject** objectReference, _COM_Outptr_opt_result_maybenull_ IKeyStore** metadata) PURE;
STDMETHOD(SetKey)(_In_ PCWSTR key, _In_opt_ IModelObject* object, _In_opt_ IKeyStore* metadata) PURE;
STDMETHOD(ClearKeys)() PURE;
STDMETHOD(EnumerateKeys)(_COM_Outptr_ IKeyEnumerator** enumerator) PURE;
STDMETHOD(EnumerateKeyReferences)(_COM_Outptr_ IKeyEnumerator** enumerator) PURE;
STDMETHOD(SetConcept)(_In_ REFIID conceptId, _In_ IUnknown* conceptInterface, _In_opt_ IKeyStore* conceptMetadata) PURE;
STDMETHOD(ClearConcepts)() PURE;
STDMETHOD(GetRawReference)(_In_ SymbolKind kind, _In_ PCWSTR name, _In_ ULONG searchFlags, _COM_Errorptr_ IModelObject** object) PURE;
STDMETHOD(EnumerateRawReferences)(_In_ SymbolKind kind, _In_ ULONG searchFlags, _COM_Outptr_ IRawEnumerator** enumerator) PURE;
STDMETHOD(SetContextForDataModel)(_In_ IModelObject* dataModelObject, _In_ IUnknown* context) PURE;
STDMETHOD(GetContextForDataModel)(_In_ IModelObject* dataModelObject, _Out_ IUnknown** context) PURE;
STDMETHOD(Compare)(_In_ IModelObject* other, _COM_Outptr_opt_result_maybenull_ IModelObject **ppResult) PURE;
STDMETHOD(IsEqualTo)(_In_ IModelObject* other, _Out_ bool* equal) PURE;
}
Basismethoden
Im Folgenden finden Sie allgemeine Methoden, die auf jede Art von Objekt anwendbar sind, das durch ein IModelObject repräsentiert wird.
STDMETHOD(GetKind)(_Out_ ModelObjectKind *kind) PURE;
STDMETHOD(GetContext)(_COM_Outptr_result_maybenull_ IDebugHostContext** context) PURE;
STDMETHOD(GetIntrinsicValue)(_Out_ VARIANT* intrinsicData);
STDMETHOD(GetIntrinsicValueAs)(_In_ VARTYPE vt, _Out_ VARIANT* intrinsicData) PURE;
STDMETHOD(Compare)(_In_ IModelObject* other, _COM_Outptr_opt_result_maybenull_ IModelObject **ppResult) PURE;
STDMETHOD(IsEqualTo)(_In_ IModelObject* other, _Out_ bool* equal) PURE;
STDMETHOD(Dereference)(_COM_Errorptr_ IModelObject** object) PURE;
Die Methode GetKind gibt zurück, um welche Art von Objekt es sich innerhalb des IModelObjects handelt.
Die Methode GetContext gibt den Host-Kontext zurück, der dem Objekt zugeordnet ist.
Die Methode GetIntrinsicValue gibt das Element zurück, das innerhalb eines IModelObjects enthalten ist. Diese Methode darf ausschließlich für IModelObject-Schnittstellen aufgerufen werden, die ein boxed intrinsic oder eine bestimmte Schnittstelle darstellen, die boxed ist. Sie kann nicht für native Objekte, Objekte ohne Wert, synthetische Objekte und Referenzobjekte aufgerufen werden. Die Methode GetIntrinsicValueAs verhält sich ähnlich wie die Methode GetIntrinsicValue mit der Ausnahme, dass sie den Wert in den angegebenen Variantentyp konvertiert. Wenn die Konvertierung nicht durchgeführt werden kann, gibt die Methode einen Fehler zurück.
Die Methode IsEqualTo vergleicht zwei Modellobjekte und gibt zurück, ob sie wertmäßig identisch sind. Bei Objekten, die eine Reihenfolge haben, ist die Rückgabe von WAHR durch diese Methode gleichbedeutend mit der Rückgabe von 0 durch die Compare-Methode. Bei Objekten, die keine Reihenfolge haben, aber gleichwertig sind, schlägt die Compare-Methode fehl, diese jedoch nicht. Die Bedeutung eines wertbasierten Vergleichs wird durch den Typ des Objekts definiert. Gegenwärtig ist dies nur für intrinsische Typen und Fehlerobjekte definiert. Es gibt derzeit kein Datenmodellkonzept für Gleichwertigkeit.
Die Methode Dereference dereferenziert ein Objekt. Mit dieser Methode können Sie eine datenmodellbasierte Referenz (ObjectTargetObjectReference, ObjectKeyReference) oder eine native Sprachreferenz (einen Zeiger oder eine Sprachreferenz) dereferenzieren. Es ist wichtig zu beachten, dass diese Methode eine einzelne Ebene der Referenzsemantik des Objekts aufhebt. Es ist durchaus möglich, dass beispielsweise eine Instanz des Datenmodells auf eine Sprachreferenz verweist. In einem solchen Fall würde der erste Aufruf der Methode Dereference die Datenmodellreferenz entfernen und die Sprachreferenz belassen. Ein Aufruf von Dereference für das resultierende Objekt würde anschließend die Sprachreferenz entfernen und den nativen Wert unter dieser Referenz zurückgeben.
Schlüsselmanipulationsmethoden
Jedes synthetische Objekt, das ein Wörterbuch aus Schlüssel/Wert/Metadaten-Tupeln ist, verfügt über eine Reihe von Methoden zur Manipulation dieser Schlüssel, Werte und der damit verbundenen Metadaten.
Die wertbasierten Formen der APIs sind:
STDMETHOD(GetKeyValue)(_In_ PCWSTR key, _COM_Errorptr_opt_ IModelObject** object, _COM_Outptr_opt_result_maybenull_ IKeyStore** metadata) PURE;
STDMETHOD(SetKeyValue)(_In_ PCWSTR key, _In_opt_ IModelObject* object) PURE;
STDMETHOD(EnumerateKeyValues)(_COM_Outptr_ IKeyEnumerator** enumerator) PURE;
The key based forms of the APIs (including those used for key creation) are:
STDMETHOD(GetKey)(_In_ PCWSTR key, _COM_Errorptr_opt_ IModelObject** object, _COM_Outptr_opt_result_maybenull_ IKeyStore** metadata) PURE;
STDMETHOD(SetKey)(_In_ PCWSTR key, _In_opt_ IModelObject* object, _In_opt_ IKeyStore* metadata) PURE;
STDMETHOD(EnumerateKeys)(_COM_Outptr_ IKeyEnumerator** enumerator) PURE;
STDMETHOD(ClearKeys)() PURE;
Die referenzbasierten Formen der APIs sind:
STDMETHOD(GetKeyReference)(_In_ PCWSTR key, _COM_Errorptr_opt_ IModelObject** objectReference, _COM_Outptr_opt_result_maybenull_ IKeyStore** metadata) PURE;
STDMETHOD(EnumerateKeyReferences)(_COM_Outptr_ IKeyEnumerator** enumerator) PURE;
Die Methode GetKeyValue ist die erste Methode, an die sich ein Client wendet, um den Wert (und die damit verbundenen Metadaten) eines bestimmten Schlüssels nach Namen abzurufen. Handelt es sich bei dem Schlüssel um einen Property-Accessor, d. h. seinen Wert als IModelObject, das ein boxed IModelPropertyAccessor ist, ruft die GetKeyValue-Methode automatisch die GetValue-Methode des Property-Accessors auf, um den tatsächlichen Wert zu ermitteln.
Die Methode SetKeyValue ist die erste Methode, an die sich ein Client wendet, um den Wert eines Schlüssels zu setzen. Diese Methode kann nicht verwendet werden, um einen neuen Schlüssel für ein Objekt zu erstellen. Sie kann nur den Wert eines vorhandenen Schlüssels festlegen. Beachten Sie, dass viele Schlüssel schreibgeschützt sind (Beispiel: Sie werden durch einen Property Accessor implementiert, der E_NOT_IMPL von seiner SetValue-Methode zurückgibt). Diese Methode schlägt fehl, wenn sie für einen schreibgeschützten Schlüssel aufgerufen wird.
Die Methode EnumerateKeyValues ist die erste Methode, an die sich ein Client wendet, um alle Schlüssel eines Objekts aufzulisten (dies schließt alle Schlüssel ein, die irgendwo im Baum der übergeordneten Modelle implementiert sind). Es ist wichtig zu beachten, dass EnumerateKeyValues alle Schlüssel auflistet, die durch doppelte Namen im Objektbaum definiert sind. Methoden wie GetKeyValue und SetKeyValue manipulieren jedoch nur die erste Instanz eines Schlüssels mit dem gegebenen Namen, die durch die Deep-First-Traversal ermittelt wurde.
Die Methode GetKey ermittelt den Wert eines bestimmten Schlüssels (und die damit verbundenen Metadaten) anhand seines Namens. Die meisten Clients sollten stattdessen die Methode GetKeyValue verwenden. Handelt es sich bei dem Schlüssel um einen Property-Accessor, gibt der Aufruf dieser Methode den Property-Accessor (eine IModelPropertyAccessor-Schnittstelle) zurück, der in ein IModelObject gepackt ist. Im Gegensatz zu GetKeyValue löst diese Methode nicht automatisch den zugrunde liegenden Wert des Schlüssels auf, indem sie die Methode GetValue aufruft. Diese Verantwortung liegt beim Aufrufer.
Die SetKey-Methode ist die Methode, die ein Client verwenden wird, um einen Schlüssel für ein Objekt zu erstellen (und möglicherweise Metadaten mit dem erstellten Schlüssel zu verknüpfen). Wenn ein bestimmtes Objekt bereits einen Schlüssel mit dem angegebenen Namen hat, gibt es zwei Verhaltensweisen. Wenn sich der Schlüssel in der Instanz befindet, die hier angegeben ist, wird der Wert dieses Schlüssels ersetzt, als ob der ursprüngliche Schlüssel nicht existieren würde. Befindet sich der Schlüssel hingegen in der Kette der übergeordneten Datenmodelle der von this angegebenen Instanz, wird ein neuer Schlüssel mit dem angegebenen Namen in der von this angegebenen Instanz erstellt. Dies würde dazu führen, dass das Objekt zwei gleichnamige Schlüssel hat (ähnlich wie eine abgeleitete Klasse, die ein Mitglied mit demselben Namen wie eine Basisklasse überlagert).
Die Methode EnumerateKeys verhält sich ähnlich wie die Methode EnumerateKeyValues, mit der Ausnahme, dass sie nicht automatisch Eigenschaftszugriffe auf das Objekt auflöst. Das bedeutet, dass die Methode EnumerateKeys, wenn der Wert eines Schlüssels ein Property-Accessor ist, den Property-Accessor (ein IModelPropertyAccessorInterface) zurückgibt, der in ein IModelObject eingebettet ist, anstatt automatisch die Methode GetValue aufzurufen. Dies ist ähnlich wie der Unterschied zwischen GetKey und GetKeyValue.
Die Methode ClearKeys entfernt alle Schlüssel und die dazugehörigen Werte und Metadaten aus der Instanz des damit angegebenen Objekts. Diese Methode hat keine Auswirkung auf übergeordnete Modelle, die mit der jeweiligen Instanz des Objekts verknüpft sind.
Die Methode GetKeyReference sucht nach einem Schlüssel des angegebenen Namens auf dem Objekt (oder seiner übergeordneten Modellkette) und gibt eine Referenz auf diesen Schlüssel zurück, die durch eine IModelKeyReference-Schnittstelle gegeben ist, die in ein IModelObject eingebettet ist. Diese Referenz kann anschließend verwendet werden, um den Wert des Schlüssels abzurufen oder festzulegen.
Die Methode EnumerateKeyReferences verhält sich ähnlich wie die Methode EnumerateKeyValues, mit dem Unterschied, dass sie statt des Wertes des Schlüssels Referenzen auf die Schlüssel zurückgibt, die sie auflistet (über eine IModelKeyReference-Schnittstelle, die in ein IModelObject eingebettet ist). Diese Referenzen können verwendet werden, um den zugrunde liegenden Wert der Schlüssel abzurufen oder festzulegen.
Konzept-Manipulationsmethoden
Ein Modellobjekt ist nicht nur ein Wörterbuch mit Schlüssel/Wert/Metadaten-Tupeln, sondern auch ein Container für Konzepte. Ein Konzept ist etwas Abstraktes, das mit einem Objekt oder durch ein Objekt ausgeführt werden kann. Konzepte sind im Wesentlichen ein dynamischer Speicher der Schnittstellen, die ein Objekt unterstützt. Eine Reihe von Konzepten wird heute durch das Datenmodell definiert:
Konzept-Schnittstelle | Beschreibung |
---|---|
IDataModelConcept | Das Konzept ist ein übergeordnetes Modell. Wenn dieses Modell über eine registrierte Typsignatur automatisch an einen nativen Typ angehängt ist, wird die Methode InitializeObject automatisch jedes Mal aufgerufen, wenn ein neues Objekt dieses Typs instanziiert wird. |
IStringDisplayableConcept | Das Objekt kann zu Anzeigezwecken in eine Zeichenfolge umgewandelt werden. |
IIterableConcept | Das Objekt ist ein Container und kann iteriert werden. |
IIndexableConcept | Das Objekt ist ein Container und kann in einer oder mehreren Dimensionen indiziert werden (Zugriff über wahlfreien Zugriff). |
IPreferredRuntimeTypeConcept | Das Objekt versteht mehr über die von ihm abgeleiteten Typen, als das zugrunde liegende Typsystem bereitstellen kann, und möchte seine eigenen Konvertierungen von statischen in Runtime-Typen vornehmen. |
IDynamicKeyProviderConcept | Das Objekt ist ein dynamischer Schlüsselanbieter und möchte alle Abfragen von Schlüsseln aus dem Core-Datenmodell übernehmen. Diese Schnittstelle wird normalerweise als Brücke zu dynamischen Sprachen wie JavaScript verwendet. |
IDynamicConceptProviderConcept | Das Objekt ist ein dynamischer Anbieter von Konzepten und möchte alle Konzeptabfragen aus dem Core-Datenmodell übernehmen. Diese Schnittstelle wird normalerweise als Brücke zu dynamischen Sprachen wie JavaScript verwendet. |
Die folgenden Methoden auf IModelObject werden verwendet, um die Konzepte zu manipulieren, die ein Objekt unterstützt.
STDMETHOD(GetConcept)(_In_ REFIID conceptId, _COM_Outptr_ IUnknown** conceptInterface, _COM_Outptr_opt_result_maybenull_ IKeyStore** conceptMetadata) PURE;
STDMETHOD(SetConcept)(_In_ REFIID conceptId, _In_ IUnknown* conceptInterface, _In_opt_ IKeyStore* conceptMetadata) PURE;
STDMETHOD(ClearConcepts)() PURE;
Die Methode GetConcept sucht nach einem Konzept im Objekt (oder seiner übergeordneten Modellkette) und gibt einen Schnittstellenzeiger auf die Konzeptschnittstelle zurück. Das Verhalten und die Methoden auf einer Konzeptschnittstelle sind für jedes Konzept spezifisch. Es ist jedoch wichtig zu wissen, dass viele der Konzeptschnittstellen vom Aufrufer verlangen, dass er explizit das Kontextobjekt übergibt (oder das, was man traditionell als diesen Zeiger bezeichnen könnte). Es ist wichtig, sicherzustellen, dass das richtige Kontextobjekt an jede Konzeptschnittstelle übergeben wird.
Die Methode SetConcept platziert ein bestimmtes Konzept in der durch den this-Zeiger angegebenen Instanz des Objekts. Wenn ein übergeordnetes Modell, das mit der durch this angegebenen Objektinstanz verbunden ist, das Konzept ebenfalls unterstützt, hat die Implementierung in der Instanz Vorrang vor der Implementierung im übergeordneten Modell.
Die Methode ClearConcepts entfernt alle Konzepte aus der Instanz des durch this angegebenen Objekts.
Native Objektmethoden
Während sich viele Modellobjekte auf Intrinsics (z. B.: Ganzzahlen, Zeichenfolgen) oder synthetische Konstrukte (ein Wörterbuch mit Schlüssel/Wert/Metadaten-Tupeln und Konzepten) beziehen, kann sich ein Modellobjekt auch auf ein natives Konstrukt beziehen (z. B.: ein benutzerdefinierter Typ im Adressraum des Debug-Ziels). Die Schnittstelle IModelObject verfügt über eine Reihe von Methoden, die auf Informationen über solche nativen Objekte zugreifen. Diese Methoden sind:
STDMETHOD(GetRawValue)(_In_ SymbolKind kind, _In_ PCWSTR name, _In_ ULONG searchFlags, _COM_Errorptr_ IModelObject** object) PURE;
STDMETHOD(EnumerateRawValues)(_In_ SymbolKind kind, _In_ ULONG searchFlags, _COM_Outptr_ IRawEnumerator** enumerator) PURE;
STDMETHOD(TryCastToRuntimeType)(_COM_Errorptr_ IModelObject** runtimeTypedObject) PURE;
STDMETHOD(GetLocation)(_Out_ Location* location) PURE;
STDMETHOD(GetTypeInfo)(_Out_ IDebugHostType** type) PURE;
STDMETHOD(GetTargetInfo)(_Out_ Location* location, _Out_ IDebugHostType** type) PURE;
STDMETHOD(GetRawReference)(_In_ SymbolKind kind, _In_ PCWSTR name, _In_ ULONG searchFlags, _COM_Errorptr_ IModelObject** object) PURE;
STDMETHOD(EnumerateRawReferences)(_In_ SymbolKind kind, _In_ ULONG searchFlags, _COM_Outptr_ IRawEnumerator** enumerator) PURE;
Die Methode GetRawValue findet ein natives Konstrukt innerhalb des gegebenen Objekts. Ein solches Konstrukt kann ein Feld, eine Basisklasse, ein Feld in einer Basisklasse, eine Mitgliedsfunktion usw. sein.
Die Methode EnumerateRawValues listet alle nativen untergeordneten Elemente (z. B.: Felder, Basisklassen etc.) des gegebenen Objekts auf.
Die Methode TryCastToRuntimeType fordert den Debug Host auf, eine Analyse durchzuführen und den tatsächlichen Runtime-Typ (z. B.: die am meisten abgeleitete Klasse) des angegebenen Objekts zu bestimmen. Die genaue Analyse, die verwendet wird, ist spezifisch für den Debug-Host und kann RTTI (C++ Runtime Type Information), die Untersuchung der V-Table (Virtual Function Table) -Struktur des Objekts oder jedes andere Mittel umfassen, das der Host verwenden kann, um den dynamischen/Runtime-Typ zuverlässig vom statischen Typ zu bestimmen. Wenn die Konvertierung in einen Runtime-Typ fehlschlägt, bedeutet dies nicht, dass dieser Methodenaufruf fehlschlägt. In solchen Fällen gibt die Methode das angegebene Objekt (den Zeiger this) im Ausgabeargument zurück.
Die Methode GetLocation gibt den Ort des nativen Objekts zurück. Obwohl es sich bei einem solchen Ort typischerweise um eine virtuelle Adresse innerhalb des Adressraums des Debug-Ziels handelt, ist dies nicht zwangsläufig der Fall. Der von dieser Methode zurückgegebene Ort ist ein abstrakter Ort, bei dem es sich um eine virtuelle Adresse handeln kann, der eine Platzierung innerhalb eines Registers oder Subregisters anzeigen kann oder einen anderen beliebigen Adressraum, wie er vom Debug-Host definiert wurde, anzeigen kann. Wenn das Feld HostDefined des resultierenden Location-Objekts 0 ist, bedeutet dies, dass es sich bei dem Ort tatsächlich um eine virtuelle Adresse handelt. Eine solche virtuelle Adresse kann durch Untersuchung des Feldes Offset des resultierenden Ortes ermittelt werden. Jeder Wert ungleich Null im Feld HostDefined weist auf einen alternativen Adressraum hin, in dem das Feld Offset der Offset innerhalb dieses Adressraums ist. Die genaue Bedeutung von HostDefined-Werten, die nicht Null sind, sind nur für den Debug-Host sichtbar.
Die Methode GetTypeInfo gibt den nativen Typ des angegebenen Objekts zurück. Wenn dem Objekt keine Informationen über den nativen Typ zugeordnet sind (Beispiel.: es ist ein Intrinsic etc., ist der Aufruf trotzdem erfolgreich, gibt aber Null zurück.
Die Methode GetTargetInfo ist eine Kombination aus den Methoden GetLocation und GetTypeInfo, die sowohl den abstrakten Ort als auch den nativen Typ des angegebenen Objekts zurückgibt.
Die Methode GetRawReference findet ein natives Konstrukt innerhalb des angegebenen Objekts und gibt eine Referenz darauf zurück. Ein solches Konstrukt kann ein Feld, eine Basisklasse, ein Feld in einer Basisklasse, eine Mitgliedsfunktion usw. sein. Es ist wichtig, die hier zurückgegebene Referenz (ein Objekt vom Typ ObjectTargetObjectReference) von einer Sprachreferenz zu unterscheiden (Beispiel: eine Referenz im Stil von C++ & oder &&).
Die Methode EnumerateRawReferences listet Verweise auf alle nativen untergeordneten Elemente (z. B. Felder, Basisklassen etc.) des gegebenen Objekts auf.
Erweiterbare Methoden
Wie bereits beschrieben, verhält sich ein Modellobjekt sehr ähnlich wie ein JavaScript-Objekt und seine Prototypenkette. Neben der Instanz, die durch eine bestimmte IModelObject-Schnittstelle repräsentiert wird, kann es eine beliebige Anzahl von übergeordneten Modellen geben, die mit dem Objekt verbunden sind (von denen jedes wiederum eine beliebige Anzahl von übergeordneten Modellen haben kann). Dies ist das primäre Mittel zur Erweiterung des Datenmodells. Wenn eine bestimmte Eigenschaft oder ein bestimmtes Konzept in einer bestimmten Instanz nicht gefunden werden kann, wird eine Tiefensuche des Objektbaums (definiert durch die übergeordneten Modelle) mit Root in der Instanz durchgeführt.
Die folgenden Methoden manipulieren die Kette der übergeordneten Modelle, die mit einer bestimmten IModelObject-Instanz verbunden sind:
STDMETHOD(GetNumberOfParentModels)(_Out_ ULONG64* numModels) PURE;
STDMETHOD(GetParentModel)(_In_ ULONG64 i, _COM_Outptr_ IModelObject **model, _COM_Outptr_result_maybenull_ IModelObject **contextObject) PURE;
STDMETHOD(AddParentModel)(_In_ IModelObject* model, _In_opt_ IModelObject* contextObject, _In_ bool override) PURE;
STDMETHOD(RemoveParentModel)(_In_ IModelObject* model) PURE;
STDMETHOD(SetContextForDataModel)(_In_ IModelObject* dataModelObject, _In_ IUnknown* context) PURE;
STDMETHOD(GetContextForDataModel)(_In_ IModelObject* dataModelObject, _Out_ IUnknown** context) PURE;
Die Methode GetNumberOfParentModels gibt die Anzahl der übergeordneten Modelle zurück, die mit der gegebenen Instanz verbunden sind. Die übergeordneten Modelle werden in der linearen Reihenfolge der übergeordneten Modellkette in der Tiefe nach Eigenschaften durchsucht.
Die Methode GetParentModel gibt das i-te übergeordnete Modell in der Kette der übergeordneten Modelle des angegebenen Objekts zurück. Übergeordnete Modelle werden in der linearen Reihenfolge, in der sie hinzugefügt oder aufgelistet werden, nach einer Eigenschaft oder einem Konzept durchsucht. Das übergeordnete Modell mit dem Index i von Null wird (hierarchisch) vor dem übergeordneten Modell mit dem Index i + 1 gesucht.
Die Methode AddParentModel fügt ein neues übergeordnetes Modell zu dem gegebenen Objekt hinzu. Ein solches Modell kann am Ende der Suchkette (das Argument override wird als Falsch angegeben) oder am Anfang der Suchkette (das Argument override wird als Wahr angegeben) hinzugefügt werden. Darüber hinaus kann jedes übergeordnete Modell optional den Kontext (den semantischen this-Zeiger) für eine beliebige Eigenschaft oder ein Konzept des übergeordneten Modells (oder eines beliebigen Modells in der übergeordneten Hierarchie) anpassen. Die Kontextanpassung wird nur selten verwendet, bietet aber die Möglichkeit, einige leistungsfähige Konzepte wie die Einbettung von Objekten, die Konstruktion von Namespaces usw. zu nutzen.
Die Funktion RemoveParentModel entfernt ein angegebenes übergeordnetes Modell aus der übergeordneten Suchkette des angegebenen Objekts.
Die Methode SetContextForDataModel wird von der Implementierung eines Datenmodells verwendet, um Implementierungsdaten in Instanzobjekten zu platzieren. Konzeptionell enthält jedes IModelObject (nennen wir es der Einfachheit halber Instanz) eine Hash-Tabelle mit dem Status. Die Hash-Tabelle wird von einem anderen IModelObject (nennen wir es der Einfachheit halber das Datenmodell) indiziert, das sich in der übergeordneten Modellhierarchie der Instanz befindet. Der in diesem Hash enthaltene Wert ist ein Satz von referenzierten Statusinformationen, die durch eine IUnknown-Instanz dargestellt werden. Sobald das Datenmodell diesen Status für die Instanz festgelegt hat, kann es beliebige Implementierungsdaten speichern, die z. B. über Property-Getter abgerufen werden können.
Die Methode GetContextForDataModel wird verwendet, um Kontextinformationen abzurufen, die mit einem vorherigen Aufruf von SetContextForDataModel eingerichtet wurden. Damit werden Statusinformationen abgerufen, die für ein Instanzobjekt durch ein weiter oben in der übergeordneten Modellhierarchie des Instanzobjekts angesiedeltes Dataset eingerichtet wurden. Weitere Einzelheiten über diesen Kontext/Status und seine Bedeutung finden Sie in der Dokumentation zu SetContextForDataModel.
Debugger-Datenmodell-Core-Objekttypen
Ein Objekt im Datenmodell ähnelt der Definition von Object in .NET. Es ist der generische Container, in den Konstrukte, die das Datenmodell versteht, eingebettet werden können. Neben nativen Objekten und synthetischen (dynamischen) Objekten gibt es eine Reihe von Core-Objekttypen, die in den Container eines IModelObjects eingefügt (oder gepackt) werden können. Der Container, in dem die meisten dieser Werte platziert werden, ist ein standardmäßiger COM/OLE VARIANT mit einer Reihe zusätzlicher Einschränkungen für das, was dieser VARIANT enthalten kann. Die grundlegendsten Typen dieser Werte sind:
- 8-Bit-Werte ohne Vorzeichen und mit Vorzeichen (VT_UI1, VT_I1)
- 16-Bit-Werte ohne Vorzeichen und mit Vorzeichen (VT_UI2, VT_UI2)
- 32-Bit-Werte ohne Vorzeichen und mit Vorzeichen (VT_UI4, VT_I4)
- 64-Bit-Werte ohne Vorzeichen und mit Vorzeichen (VT_UI8, VT_I8)
- Einfache und doppelt genaue Fließkommazahlen (VT_R4, VT_R8)
- Zeichenfolgen (VT_BSTR)
- Boolesche Werte (VT_BOOL)
Zusätzlich zu diesen Basistypen werden eine Reihe von Core-Datenmodellobjekten in IModelObject platziert, das durch VT_UNKNOWN definiert ist, wobei das gespeicherte IUnknown garantiert eine bestimmte Schnittstelle implementiert. Zu diesen Typen zählen:
- Property-Accessors (IModelPropertyAccessor)
- Methodenobjekte (IModelMethod)
- Schlüsselreferenzobjekte (IModelKeyReference oder IModelKeyReference2)
- Context-Objekte (IDebugModelHostContext)
Property-Accessors: IModelPropertyAccessor
Ein Property-Accessor im Datenmodell ist eine Implementierung der Schnittstelle IModelPropertyAccessor, die in ein IModelObject eingebettet ist. Das Modellobjekt gibt eine Art ObjectPropertyAccessor zurück, wenn es abgefragt wird. Der Intrinsic-Value ist ein VT_UNKNOWN, der für IModelPropertyAccessor garantiert abfragbar ist. Im Prozess wird garantiert, dass er statisch auf IModelPropertyAccessor gecastet werden kann.
Ein Property-Accessor ist ein indirekter Weg, um einen Methodenaufruf zum Abrufen und Festlegen eines Schlüsselwertes im Datenmodell zu erhalten. Wenn der Wert eines bestimmten Schlüssels ein Property-Accessor ist, bemerken die Methoden GetKeyValue und SetKeyValue dies automatisch und rufen die dem Property-Accessor zugrunde liegenden Methoden GetValue bzw. SetValue entsprechend auf.
Die Schnittstelle IModelPropertyAccessor ist wie folgt definiert:
DECLARE_INTERFACE_(IModelPropertyAccessor, IUnknown)
{
STDMETHOD(GetValue)(_In_ PCWSTR key, _In_opt_ IModelObject* contextObject, _COM_Outptr_ IModelObject** value) PURE;
STDMETHOD(SetValue)(_In_ PCWSTR key, _In_opt_ IModelObject* contextObject, _In_ IModelObject* value) PURE;
}
Die GetValue-Methode ist der Getter für den Property-Accessor. Sie wird immer dann aufgerufen, wenn ein Client den zugrunde liegenden Wert der Eigenschaft abrufen möchte. Beachten Sie, dass jeder Aufrufer, der einen Property-Accessor direkt abruft, dafür verantwortlich ist, den Schlüsselnamen und das genaue Instanzobjekt (this-Zeiger) an die GetValue-Methode des Property-Accessors zu übergeben.
Die SetValue-Methode ist der Setter für den Property-Accessor. Sie wird immer dann aufgerufen, wenn ein Client der zugrunde liegenden Eigenschaft einen Wert zuweisen möchte. Viele Eigenschaften sind schreibgeschützt. In solchen Fällen gibt der Aufruf der SetValue-Methode E_NOTIMPL zurück. Beachten Sie, dass jeder Aufrufer, der einen Property-Accessor direkt abfragt, dafür verantwortlich ist, den Schlüsselnamen und das genaue Instanz-Objekt (this-Zeiger) an die Methode SetValue des Property-Accessors zu übergeben.
Methoden: IModelMethod
Eine Methode im Datenmodell ist eine Implementierung der Schnittstelle IModelMethod, die in ein IModelObject eingebettet ist. Das Modellobjekt gibt eine Art ObjectMethod zurück, wenn es abgefragt wird, und der intrinsische Wert ist ein VT_UNKNOWN, der garantiert für IModelMethod abfragbar ist. Bei der Verarbeitung wird garantiert, dass er statisch in IModelMethod umgewandelt werden kann. Alle Methoden im Datenmodell sind dynamischer Natur. Sie nehmen als Eingabe einen Satz von 0 oder mehr Argumenten und geben einen einzelnen Ausgabewert zurück. Es gibt keine Überladungsauflösung und keine Metadaten über Parameternamen, Typen oder Erwartungen.
Die IModelMethod-Schnittstelle ist wie folgt definiert:
DECLARE_INTERFACE_(IModelMethod, IUnknown)
{
STDMETHOD(Call)(_In_opt_ IModelObject *pContextObject, _In_ ULONG64 argCount, _In_reads_(argCount) IModelObject **ppArguments, _COM_Errorptr_ IModelObject **ppResult, _COM_Outptr_opt_result_maybenull_ IKeyStore **ppMetadata) PURE;
}
Die Call-Methode ist die Art und Weise, in der jede im Datenmodell definierte Methode aufgerufen wird. Der Aufrufer ist dafür verantwortlich, ein korrektes Instanz-Objekt (this-Zeiger) und eine beliebige Menge von Argumenten zu übergeben. Das Ergebnis der Methode und alle optionalen Metadaten, die mit diesem Ergebnis verbunden sind, werden zurückgegeben. Methoden, die logisch keinen Wert zurückgeben, müssen dennoch ein gültiges IModelObject zurückgeben. In einem solchen Fall ist das IModelObject ein boxed no value. Für den Fall, dass eine Methode fehlschlägt, kann sie optionale erweiterte Fehlerinformationen im Eingabeargument zurückgeben (auch wenn das zurückgegebene HRESULT ein Fehler ist). Es ist zwingend erforderlich, dass der Aufrufer dies überprüft.
Schlüsselreferenzen: IModelKeyReference oder IModelKeyReference2
Eine Schlüsselreferenz ist im Wesentlichen ein Handle für einen Schlüssel eines bestimmten Objekts. Ein Client kann ein solches Handle über Methoden wie GetKeyReference abrufen und das Handle später verwenden, um den Wert des Schlüssels abzurufen oder festzulegen, ohne unbedingt das ursprüngliche Objekt zu behalten. Diese Art von Objekt ist eine Implementierung der Schnittstelle IModelKeyReference oder IModelKeyReference2, die in ein IModelObject eingebettet ist. Das Modellobjekt gibt eine Art ObjectKeyReference zurück, wenn es abgefragt wird. Der intrinsische Wert ist dann ein VT_UNKNOWN, der garantiert für IModelKeyReference abfragbar ist. Im Prozess wird garantiert, dass er statisch in IModelKeyReference umgewandelt werden kann.
Die Schlüsselreferenzschnittstelle ist wie folgt definiert:
DECLARE_INTERFACE_(IModelKeyReference2, IModelKeyReference)
{
STDMETHOD(GetKeyName)(_Out_ BSTR* keyName) PURE;
STDMETHOD(GetOriginalObject)(_COM_Outptr_ IModelObject** originalObject) PURE;
STDMETHOD(GetContextObject)(_COM_Outptr_ IModelObject** containingObject) PURE;
STDMETHOD(GetKey)(_COM_Errorptr_opt_ IModelObject** object, _COM_Outptr_opt_result_maybenull_ IKeyStore** metadata) PURE;
STDMETHOD(GetKeyValue)(_COM_Errorptr_opt_ IModelObject** object, _COM_Outptr_opt_result_maybenull_ IKeyStore** metadata) PURE;
STDMETHOD(SetKey)(_In_opt_ IModelObject* object, _In_opt_ IKeyStore* metadata) PURE;
STDMETHOD(SetKeyValue)(_In_ IModelObject* object) PURE;
STDMETHOD(OverrideContextObject)(_In_ IModelObject* newContextObject) PURE;
}
Die Methode GetKeyName gibt den Namen des Schlüssels zurück, zu dem diese Schlüsselreferenz ein Handle ist. Die zurückgegebene Zeichenfolge ist ein Standard-BSTR und muss durch einen Aufruf von SysFreeString freigegeben werden.
Die Methode GetOriginalObject gibt das Instanzobjekt zurück, aus dem die Schlüsselreferenz erstellt wurde. Beachten Sie, dass sich der Schlüssel selbst in einem übergeordneten Modell des Instanzobjekts befinden kann.
Die Methode GetContextObject gibt den Kontext (this-Zeiger) zurück, der an die GetValue- oder SetValue-Methode eines Property-Accessors übergeben wird, wenn der betreffende Schlüssel auf einen Property-Accessor verweist. Das hier zurückgegebene Kontextobjekt kann mit dem von GetOriginalObject geholten Originalobjekt identisch sein oder auch nicht. Wenn sich ein Schlüssel auf ein übergeordnetes Modell bezieht und es einen mit diesem übergeordneten Modell verknüpften Kontextanpasser gibt, ist das ursprüngliche Objekt das Objekt der Instanz, für die GetKeyReference oder EnumerateKeyReferences aufgerufen wurde. Das Kontextobjekt ist das, was aus dem letzten Kontextanpasser zwischen dem Originalobjekt und dem übergeordneten Modell hervorgeht, das den Schlüssel enthält, zu dem diese Schlüsselreferenz ein Handle ist. Wenn es keine Kontextanpasser gibt, sind das ursprüngliche Objekt und das Kontextobjekt identisch.
Die Methode GetKey für eine Schlüsselreferenz verhält sich wie die Methode GetKey für IModelObject. Sie gibt den Wert des zugrunde liegenden Schlüssels und alle mit dem Schlüssel verbundenen Metadaten zurück. Wenn der Wert des Schlüssels ein Property-Accessor ist, wird der Property-Accessor (IModelPropertyAccessor) zurückgegeben, der in ein IModelObject eingebettet ist. Diese Methode ruft nicht die zugrunde liegenden Methoden GetValue oder SetValue des Property-Accessors auf.
Die Methode GetKeyValue für eine Schlüsselreferenz verhält sich wie die Methode GetKeyValue für IModelObject. Sie gibt den Wert des zugrunde liegenden Schlüssels und alle mit dem Schlüssel verbundenen Metadaten zurück. Handelt es sich bei dem Wert des Schlüssels um einen Property-Accessor, wird automatisch die zugrunde liegende GetValue-Methode des Property-Accessors aufgerufen.
Die Methode SetKey für eine Schlüsselreferenz verhält sich wie die Methode SetKey für IModelObject. Sie weist den Wert des Schlüssels zu. Wenn der ursprüngliche Schlüssel ein Property-Accessor war, wird der Property-Accessor durch diese Methode ersetzt. Sie ruft nicht die Methode SetValue des Property-Accessors auf.
Die Methode SetKeyValue für eine Schlüsselreferenz verhält sich wie die Methode SetKeyValue für IModelObject. Sie weist den Wert des Schlüssels zu. Wenn der ursprüngliche Schlüssel ein Property-Accessor war, wird die zugrunde liegende SetValue-Methode des Property-Accessors aufgerufen, anstatt den Property-Accessor selbst zu ersetzen.
Die OverrideContextObject-Methode (nur bei IModelKeyReference2 vorhanden) ist eine erweiterte Methode, mit der Sie das Kontextobjekt, das diese Schlüsselreferenz an die GetValue- oder SetValue-Methoden des zugrunde liegenden Property-Accessors weitergibt, dauerhaft ändern können. Das Objekt, das an diese Methode übergeben wird, wird auch von einem Aufruf von GetContextObject zurückgegeben. Diese Methode kann von Skriptanbietern verwendet werden, um bestimmte dynamische Sprachverhaltensweisen zu replizieren. Die meisten Clients sollten diese Methode nicht aufrufen.
Context-Objekte: IDebugHostContext
Context-Objekte sind intransparente Blob-Informationen, die der Debug-Host (in Zusammenarbeit mit dem Datenmodell) mit jedem Objekt verknüpft. Dazu gehören Dinge wie der Prozesskontext oder der Adressraum, aus dem die Informationen stammen usw. Ein Kontextobjekt ist eine Implementierung von IDebugHostContext, die in ein IModelObject eingebettet ist. Beachten Sie, dass IDebugHostContext eine vom Host definierte Schnittstelle ist. Ein Client wird diese Schnittstelle niemals implementieren.
Weitere Informationen über Context-Objekte finden Sie unter Debugger-Datenmodell C++ Host-Schnittstellen in Debugger-Datenmodell C++ Schnittstellen.
Der Datenmodell-Manager
Die Core-Schnittstelle zum Datenmodell-Manager, IDataModelManager2 (oder dem früheren IDataModelManager) ist wie folgt definiert:
DECLARE_INTERFACE_(IDataModelManager2, IDataModelManager)
{
//
// IDataModelManager:
//
STDMETHOD(Close)() PURE;
STDMETHOD(CreateNoValue)(_Out_ IModelObject** object) PURE;
STDMETHOD(CreateErrorObject)(_In_ HRESULT hrError, _In_opt_ PCWSTR pwszMessage, _COM_Outptr_ IModelObject** object) PURE;
STDMETHOD(CreateTypedObject)(_In_opt_ IDebugHostContext* context, _In_ Location objectLocation, _In_ IDebugHostType* objectType, _COM_Errorptr_ IModelObject** object) PURE;
STDMETHOD(CreateTypedObjectReference)(_In_opt_ IDebugHostContext* context, _In_ Location objectLocation, _In_ IDebugHostType* objectType, _COM_Errorptr_ IModelObject** object) PURE;
STDMETHOD(CreateSyntheticObject)(_In_opt_ IDebugHostContext* context, _COM_Outptr_ IModelObject** object) PURE;
STDMETHOD(CreateDataModelObject)(_In_ IDataModelConcept* dataModel, _COM_Outptr_ IModelObject** object) PURE;
STDMETHOD(CreateIntrinsicObject)(_In_ ModelObjectKind objectKind, _In_ VARIANT* intrinsicData, _COM_Outptr_ IModelObject** object) PURE;
STDMETHOD(CreateTypedIntrinsicObject)(_In_ VARIANT* intrinsicData, _In_ IDebugHostType* type, _COM_Outptr_ IModelObject** object) PURE;
STDMETHOD(GetModelForTypeSignature)(_In_ IDebugHostTypeSignature* typeSignature, _Out_ IModelObject** dataModel) PURE;
STDMETHOD(GetModelForType)(_In_ IDebugHostType* type, _COM_Outptr_ IModelObject** dataModel, _COM_Outptr_opt_ IDebugHostTypeSignature** typeSignature, _COM_Outptr_opt_ IDebugHostSymbolEnumerator** wildcardMatches) PURE;
STDMETHOD(RegisterModelForTypeSignature)(_In_ IDebugHostTypeSignature* typeSignature, _In_ IModelObject* dataModel) PURE;
STDMETHOD(UnregisterModelForTypeSignature)(_In_ IModelObject* dataModel, _In_opt_ IDebugHostTypeSignature* typeSignature) PURE;
STDMETHOD(RegisterExtensionForTypeSignature)(_In_ IDebugHostTypeSignature* typeSignature, _In_ IModelObject* dataModel) PURE;
STDMETHOD(UnregisterExtensionForTypeSignature)(_In_ IModelObject* dataModel, _In_opt_ IDebugHostTypeSignature* typeSignature) PURE;
STDMETHOD(CreateMetadataStore)(_In_opt_ IKeyStore* parentStore, _COM_Outptr_ IKeyStore** metadataStore) PURE;
STDMETHOD(GetRootNamespace)(_COM_Outptr_ IModelObject** rootNamespace) PURE;
STDMETHOD(RegisterNamedModel)(_In_ PCWSTR modelName, _In_ IModelObject *modeObject) PURE;
STDMETHOD(UnregisterNamedModel)(_In_ PCWSTR modelName) PURE;
STDMETHOD(AcquireNamedModel)(_In_ PCWSTR modelName, _COM_Outptr_ IModelObject **modelObject) PURE;
//
// IDataModelManager2:
//
STDMETHOD(AcquireSubNamespace)(_In_ PCWSTR modelName, _In_ PCWSTR subNamespaceModelName, _In_ PCWSTR accessName, _In_opt_ IKeyStore *metadata, _COM_Outptr_ IModelObject **namespaceModelObject) PURE;
STDMETHOD(CreateTypedIntrinsicObjectEx)(_In_opt_ IDebugHostContext* context, _In_ VARIANT* intrinsicData, _In_ IDebugHostType* type, _COM_Outptr_ IModelObject** object) PURE;
}
Management-Methoden
Der folgende Satz von Methoden wird von der Anwendung (z. B. Debugger) verwendet, die das Datenmodell hostet.
STDMETHOD(Close)() PURE;
Die Methode Close wird von einer Anwendung (z. B.: Debugger), die das Datenmodell hostet, auf dem Datenmodell-Manager aufgerufen, um den Shutdown-Prozess des Datenmodell-Managers zu starten. Ein Host des Datenmodells, der die Close-Methode nicht aufruft, bevor er seine letzte Referenz auf den Datenmodell-Manager freigibt, kann zu undefiniertem Verhalten führen, einschließlich, aber nicht beschränkt auf erhebliche Datenverluste in der Verwaltungsinfrastruktur für das Datenmodell.
Objekterstellung / Boxing-Methoden
Die folgenden Methoden werden verwendet, um neue Objekte zu erstellen oder um Werte in einem IModelObject – der Kernschnittstelle des Datenmodells – festzulegen.
STDMETHOD(CreateNoValue)(_Out_ IModelObject** object) PURE;
STDMETHOD(CreateErrorObject)(_In_ HRESULT hrError, _In_opt_ PCWSTR pwszMessage, _COM_Outptr_ IModelObject** object) PURE;
STDMETHOD(CreateTypedObject)(_In_opt_ IDebugHostContext* context, _In_ Location objectLocation, _In_ IDebugHostType* objectType, _COM_Errorptr_ IModelObject** object) PURE;
STDMETHOD(CreateTypedObjectReference)(_In_opt_ IDebugHostContext* context, _In_ Location objectLocation, _In_ IDebugHostType* objectType, _COM_Errorptr_ IModelObject** object) PURE;
STDMETHOD(CreateSyntheticObject)(_In_opt_ IDebugHostContext* context, _COM_Outptr_ IModelObject** object) PURE;
STDMETHOD(CreateDataModelObject)(_In_ IDataModelConcept* dataModel, _COM_Outptr_ IModelObject** object) PURE;
STDMETHOD(CreateIntrinsicObject)(_In_ ModelObjectKind objectKind, _In_ VARIANT* intrinsicData, _COM_Outptr_ IModelObject** object) PURE;
STDMETHOD(CreateTypedIntrinsicObject)(_In_ VARIANT* intrinsicData, _In_ IDebugHostType* type, _COM_Outptr_ IModelObject** object) PURE;
STDMETHOD(CreateMetadataStore)(_In_opt_ IKeyStore* parentStore, _COM_Outptr_ IKeyStore** metadataStore) PURE;
STDMETHOD(CreateTypedIntrinsicObjectEx)(_In_opt_ IDebugHostContext* context, _In_ VARIANT* intrinsicData, _In_ IDebugHostType* type, _COM_Outptr_ IModelObject** object) PURE;
Die Methode CreateNoValue erstellt ein „no value“-Objekt, packt es in ein IModelObject und gibt es zurück. Das zurückgegebene Modellobjekt hat eine Art ObjectNoValue.
Ein „no value“-Objekt hat mehrere semantische Bedeutungen:
- (Je nach Sprache) kann es als das semantische Äquivalent von Void, Null oder Undefined betrachtet werden
- Wenn die GetValue-Methode eines Property-Accessors erfolgreich ist und ein „no value“-Objekt zurückgibt, bedeutet dies, dass die betreffende Eigenschaft für die gegebene Instanz keinen Wert hat und so behandelt werden sollte, als würde die Eigenschaft für diese Instanz nicht existieren.
- Datenmodellmethoden, die semantisch keinen Rückgabewert haben, verwenden dies als Sentinel, um dies zu signalisieren (da eine Methode ein gültiges IModelObject zurückgeben muss).
Die Methode CreateErrorObject erstellt ein „Fehlerobjekt“. Das Datenmodell kennt keine Ausnahmen und keinen Exception Flow. Ein Fehler kann auf zwei Arten aus einer Eigenschaft/Methode hervorgehen:
- Ein einzelnes Fehler-HRESULT ohne erweiterte Fehlerinformationen. Entweder gibt es keine weiteren Informationen, die für den Fehler angegeben werden können, oder der Fehler selbst ist anhand des zurückgegebenen HRESULT selbsterklärend.
- Ein einziges Fehler-HRESULT in Verbindung mit einer erweiterten Fehlerinformation. Die erweiterten Fehlerinformationen sind ein Fehlerobjekt, das im Ausgabeargument der Eigenschaft/Methode zurückgegeben wird.
Die Methode CreateTypedObject ist die Methode, die einem Client die Möglichkeit bietet, eine Repräsentation eines nativen oder Sprachobjekts im Adressraum eines Debug-Ziels zu erstellen. Wenn der Typ des neu erstellten Objekts (wie durch das objectType-Argument vorgegeben) mit einer oder mehreren Typsignaturen übereinstimmt, die beim Datenmodell-Manager entweder als kanonische Visualisierungen oder als Erweiterungen registriert sind, werden diese übereinstimmenden Datenmodelle automatisch an das erstellte Instanzobjekt angehängt, bevor es an den Aufrufer zurückgegeben wird.
Die Methode CreateTypedObjectReference ist semantisch ähnlich wie die Methode CreateTypedObject, mit dem Unterschied, dass sie eine Referenz auf das zugrunde liegende native oder Sprachkonstrukt erstellt. Die erstellte Referenz ist ein Objekt, das eine Art ObjectTargetObjectReference hat. Es handelt sich nicht um eine native Referenz, die von der zugrunde liegenden Sprache unterstützt wird (Beispiel: ein & oder && in C++). Es ist durchaus möglich, eine ObjectTargetObjectReference zu einer C++ Referenz zu haben. Ein Objekt des Typs ObjectTargetObjectReference kann mit Hilfe der Methode Dereference auf IModelObject in den zugrunde liegenden Wert umgewandelt werden. Die Referenz kann auch an den Ausdrucksauswerter des zugrunde liegenden Hosts übergeben werden, um sie dem Wert auf sprachlich angemessene Weise wieder zuzuweisen.
Die Methode CreateSyntheticObject erstellt ein leeres Datenmodellobjekt – ein Wörterbuch mit Schlüssel/Wert/Metadaten-Tupeln und Konzepten. Zum Zeitpunkt der Erstellung gibt es weder Schlüssel noch Konzepte für das Objekt. Es ist ein unbeschriebenes Blatt, das der Aufrufer verwenden kann.
Die Methode CreateDataModelObject ist ein einfaches Hilfsmittel zum Erstellen von Objekten, die Datenmodelle sind, d. h. Objekte, die als übergeordnete Modelle an andere Objekte angehängt werden. Alle diese Objekte müssen das Datenmodellkonzept über IDataModelConcept unterstützen. Diese Methode erstellt ein neues, leeres, synthetisches Objekt ohne expliziten Kontext und fügt das übergebene IDataModelConcept als Implementierung des Datenmodellkonzepts in das neu erstellte Objekt ein. Dies kann in ähnlicher Weise mit Aufrufen von CreateSyntheticObject und SetConcept erreicht werden.
Die Methode CreateIntrinsicObject ist die Methode, die intrinsische Werte in IModelObject packt. Der Aufrufer platziert den Wert in einem COM VARIANT und ruft diese Methode auf. Der Datenmodell-Manager gibt ein IModelObject zurück, das das Objekt repräsentiert. Beachten Sie, dass diese Methode auch verwendet wird, um grundlegende, auf IUnknown basierende Typen zu packen: Property-Accessors, Methoden, Kontexte usw. In solchen Fällen gibt die objectKind-Methode an, welche Art von IUnknown-basiertem Konstrukt das Objekt darstellt, und das punkVal-Feld der übergebenen Variante ist der abgeleitete IUnknown-Typ. Der Typ muss statisch in die entsprechende Modellschnittstelle (z. B. IModelPropertyAccessor, IModelMethod, IDebugHostContext etc...) in process umwandelbar sein. Die VARIANT-Typen, die von dieser Methode unterstützt werden, sind VT_UI1, VT_I1, VT_UI2, VT_I2, VT_UI4, VT_I4, VT_UI8, VT_I8, VT_R4, VT_R8, VT_BOOL, VT_BSTR und VT_UNKNOWN (für einen speziellen Satz von IUnknown abgeleiteten Typen, wie durch die Enumerierung ModelObjectKind festgelegt.
Die Methode CreateTypedintrinsicObject ähnelt der Methode CreateIntrinsicObject mit dem Unterschied, dass sie die Möglichkeit bietet, einen nativen oder Sprachtyp mit den Daten zu verknüpfen und zusammen mit dem Box-Wert zu übertragen. Dies bietet dem Datenmodell die Möglichkeit, Konstrukte wie native Enumerationstypen (die einfach VT_UI*- oder VT_I*-Werte sind) darzustellen. Auch Zeigertypen werden mit dieser Methode erstellt. Ein nativer Zeiger im Datenmodell ist eine um Null erweiterte 64-Bit-Menge, die einen Offset in den virtuellen Adressraum des Debug-Ziels darstellt. Er ist in eine VT_UI8 gepackt und wird mit dieser Methode und einem Typ erstellt, der einen nativen bzw. Sprachzeiger angibt.
Die Methode CreateMetadataStore erstellt einen Schlüsselspeicher – einen vereinfachten Container mit Schlüssel/Wert/Metadaten-Tupeln – , der dazu dient, Metadaten zu speichern, die mit Eigenschaften und einer Vielzahl anderer Werte verknüpft werden können. Ein Metadatenspeicher kann einen einzigen übergeordneten Speicher haben (der wiederum einen einzigen übergeordneten Speicher haben kann). Wenn sich ein bestimmter Metadatenschlüssel nicht in einem bestimmten Speicher befindet, werden seine übergeordneten Speicher überprüft. Die meisten Metadaten-Speicher haben keine übergeordneten Speicher. Es bietet jedoch eine Möglichkeit, allgemeine Metadaten auf einfache Weise gemeinsam zu nutzen.
Die Methode CreateTypedIntrinsicObjectEx ist semantisch ähnlich wie die Methode CreateTypedIntrinsicObject. Der einzige Unterschied zwischen den beiden ist, dass diese Methode dem Aufrufer die Möglichkeit bietet, den Kontext anzugeben, in dem die intrinsischen Daten gültig sind. Wird kein Kontext übergeben, gelten die Daten als gültig in dem Kontext, der vom Typargument geerbt wird (wie sich CreateTypedIntrinsicObject verhält). Dies bietet die Möglichkeit, im Debug-Ziel typisierte Zeigerwerte zu erstellen, die einen spezifischeren Kontext erfordern, als er vom Typ geerbt werden kann.
Erweiterbarkeit/Registrierungsmethoden Der folgende Satz von Methoden verwaltet den Erweiterungsmechanismus des Datenmodells, der es einem Client erlaubt, bestehende Modelle zu erweitern oder zu registrieren oder das Datenmodell aufzufordern, ein bestimmtes übergeordnetes Modell automatisch an native Typen anzuhängen, die einem bestimmten Kriterium entsprechen.
STDMETHOD(GetModelForTypeSignature)(_In_ IDebugHostTypeSignature* typeSignature, _Out_ IModelObject** dataModel) PURE;
STDMETHOD(GetModelForType)(_In_ IDebugHostType* type, _COM_Outptr_ IModelObject** dataModel, _COM_Outptr_opt_ IDebugHostTypeSignature** typeSignature, _COM_Outptr_opt_ IDebugHostSymbolEnumerator** wildcardMatches) PURE;
STDMETHOD(RegisterModelForTypeSignature)(_In_ IDebugHostTypeSignature* typeSignature, _In_ IModelObject* dataModel) PURE;
STDMETHOD(UnregisterModelForTypeSignature)(_In_ IModelObject* dataModel, _In_opt_ IDebugHostTypeSignature* typeSignature) PURE;
STDMETHOD(RegisterExtensionForTypeSignature)(_In_ IDebugHostTypeSignature* typeSignature, _In_ IModelObject* dataModel) PURE;
STDMETHOD(UnregisterExtensionForTypeSignature)(_In_ IModelObject* dataModel, _In_opt_ IDebugHostTypeSignature* typeSignature) PURE;
STDMETHOD(GetRootNamespace)(_COM_Outptr_ IModelObject** rootNamespace) PURE;
STDMETHOD(RegisterNamedModel)(_In_ PCWSTR modelName, _In_ IModelObject *modeObject) PURE;
STDMETHOD(UnregisterNamedModel)(_In_ PCWSTR modelName) PURE;
STDMETHOD(AcquireNamedModel)(_In_ PCWSTR modelName, _COM_Outptr_ IModelObject **modelObject) PURE;
Die Methode GetModelForTypeSignature gibt das Datenmodell zurück, das für eine bestimmte Typsignatur durch einen vorherigen Aufruf der Methode RegisterModelForTypeSignature registriert wurde. Das von dieser Methode zurückgegebene Datenmodell wird als kanonischer Visualisierer für jeden Typ betrachtet, der mit der übergebenen Typsignatur übereinstimmt. Als kanonischer Visualisierer übernimmt dieses Datenmodell die Anzeige des Typs. Anzeige-Engines blenden standardmäßig die nativen bzw. Sprachkonstrukte des Objekts zugunsten der durch das Datenmodell dargestellten Ansicht des Objekts aus.
Die Methode GetModelForType gibt das Datenmodell zurück, das der kanonische Visualisierer für eine bestimmte Instanz des Typs ist. In der Tat findet diese Methode die am besten passende Typsignatur, die mit einem vorherigen Aufruf der Methode RegisterModelForTypeSignature registriert wurde, und gibt das zugehörige Datenmodell zurück.
Die Methode RegisterModelForTypeSignature ist die primäre Methode, die ein Aufrufer verwendet, um einen kanonischen Visualisierer für einen bestimmten Typ (oder eine Reihe von Typen) zu registrieren. Ein kanonischer Visualisierer ist ein Datenmodell, das faktisch die Anzeige eines bestimmten Typs (oder eines festgelegten Sets von Typen) übernimmt. Anstelle der nativen bzw. Sprachansicht des Typs wird in jeder Debugger-Benutzeroberfläche die Ansicht des Typs angezeigt, wie sie vom registrierten Datenmodell dargestellt wird (zusammen mit einer Möglichkeit, zur nativen bzw. Sprachansicht zurückzukehren, wenn Benutzende dies wünschen).
UnregisterModelForTypeSignature
Die Methode UnregisterModelForTypeSignature macht einen vorherigen Aufruf der Methode RegisterModelForTypeSignature rückgängig. Diese Methode kann entweder ein bestimmtes Datenmodell als kanonischen Visualisierer für Typen entfernen, die einer bestimmten Typsignatur entsprechen, oder sie kann ein bestimmtes Datenmodell als kanonischen Visualisierer für jede Typsignatur entfernen, unter der dieses Datenmodell registriert ist.
RegisterExtensionForTypeSignature
Die Methode RegisterExtensionForTypeSignature ähnelt der Methode RegisterModelForTypeSignature mit einem entscheidenden Unterschied. Das Datenmodell, das an diese Methode übergeben wird, ist nicht der kanonische Visualizer für einen Typ und übernimmt nicht die Anzeige der nativen bzw. Sprachansicht dieses Typs. Das Datenmodell, das an diese Methode übergeben wird, wird automatisch als übergeordneter Typ zu jedem konkreten Typ hinzugefügt, der der übergebenen Typsignatur entspricht. Anders als bei der Methode RegisterModelForTypeSignature gibt es keine Beschränkung für identische oder mehrdeutige Typsignaturen, die als Erweiterungen eines bestimmten Typs (oder einer Gruppe von Typen) registriert werden können. Jede Erweiterung, deren Typsignatur mit einer bestimmten konkreten Instanz eines Typs übereinstimmt, bewirkt, dass das mit dieser Methode registrierte Datenmodell automatisch an neu erstellte Objekte als übergeordnete Modelle angehängt wird. Dies bietet einer beliebigen Anzahl von Clients die Möglichkeit, einen Typ (oder eine Menge von Typen) um neue Felder oder Funktionen zu erweitern.
UnregisterExtensionForTypeSignature
Die Methode UnregisterExtensionForTypeSignature macht einen vorherigen Aufruf von RegisterExtensionForTypeSignature rückgängig. Sie hebt die Registrierung eines bestimmten Datenmodells als Erweiterung entweder für eine bestimmte Typsignatur oder als Erweiterung für alle Typsignaturen auf, für die das Datenmodell registriert wurde.
Die Methode GetRootNamespace gibt den Root-Namespace des Datenmodells zurück. Dabei handelt es sich um ein Objekt, das das Datenmodell verwaltet und in das der Debug-Host bestimmte Objekte platziert.
Die Methode RegisterNamedModel registriert ein bestimmtes Datenmodell unter einem bekannten Namen, damit es von Clients, die es erweitern möchten, gefunden werden kann. Dies ist der primäre Zweck der API – ein Datenmodell als etwas zu veröffentlichen, das erweitert werden kann, indem man das unter diesem bekannten Namen registrierte Modell abruft und ihm ein übergeordnetes Modell hinzufügt.
Die Methode UnregisterNamedModel macht einen vorherigen Aufruf von RegisterNamedModel rückgängig. Sie hebt die Verbindung zwischen einem Datenmodell und einem Namen auf, unter dem es gefunden werden kann.
Ein Aufrufer, der ein Datenmodell erweitern möchte, das unter einem bestimmten Namen registriert ist, ruft die Methode AcquireNamedModel auf, um das Objekt für das Datenmodell, das er erweitern möchte, abzurufen. Diese Methode gibt das Datenmodell zurück, das durch einen vorherigen Aufruf der Methode RegisterNamedModel registriert wurde. Da der primäre Zweck der AcquireNamedModel-Methode darin besteht, das Modell zu erweitern, verhält sich diese Methode besonders, wenn noch kein Modell unter dem angegebenen Namen registriert wurde. Wurde noch kein Modell unter dem angegebenen Namen registriert, wird ein Stub-Objekt erstellt, vorübergehend unter dem angegebenen Namen registriert und an den Aufrufer zurückgegeben. Wenn das echte Datenmodell über einen Aufruf der Methode RegisterNamedModel registriert wird, werden alle Änderungen, die am Stub-Objekt vorgenommen wurden, auch am echten Modell vorgenommen. Dadurch werden viele Probleme mit Abhängigkeiten beim Laden von Komponenten, die sich gegenseitig erweitern, beseitigt.
Hilfsmethoden
Bei den folgenden Methoden handelt es sich um allgemeine Hilfsmethoden, die Sie bei der Durchführung komplexer Vorgänge mit Objekten im Datenmodell unterstützen. Sie können diese Aktionen zwar auch mit anderen Methoden für das Datenmodell oder seine Objekte durchführen, aber diese Hilfsmethoden machen es erheblich einfacher:
STDMETHOD(AcquireSubNamespace)(_In_ PCWSTR modelName, _In_ PCWSTR subNamespaceModelName, _In_ PCWSTR accessName, _In_opt_ IKeyStore *metadata, _COM_Outptr_ IModelObject **namespaceModelObject) PURE;
Die AcquireSubNamespace-Methode hilft bei der Konstruktion von etwas, das traditionell eher wie ein Sprach-Namespace als ein neues Objekt in einer dynamischen Sprache aussieht. Wenn ein Aufrufer beispielsweise die Eigenschaften eines Prozessobjekts kategorisieren möchte, um das Prozessobjekt besser zu organisieren und die Eigenschaften leichter auffindbar zu machen, wäre eine Methode, dies zu tun, ein Unterobjekt für jede Kategorie des Prozessobjekts zu erstellen und diese Eigenschaften in diesem Objekt zu platzieren.
Weitere Informationen
Dieses Thema ist Teil einer Serie, in der die Schnittstellen beschrieben werden, auf die von C++ aus zugegriffen werden kann, und wie man sie verwendet, um eine C++ basierte Debugger-Erweiterung zu erstellen, und wie man andere Datenmodellkonstrukte (z. B. JavaScript oder NatVis) von einer C++ -Datenmodellerweiterung aus nutzen kann.
Debugger Datenmodell C++ – Übersicht
Debugger Datenmodell C++ – Schnittstellen
Debugger Datenmodell C++ – Zusätzliche Schnittstellen