Verwenden der Metadaten-API und der Token
Aktualisiert: November 2007
Die Metadaten-APIs können von C++ aufgerufen werden. Wie Metadaten-APIs verwendet werden, hängt zum Teil von der Art des Clients ab, der sie verwendet. Die meisten Metadaten-API-Clients können in eine der folgenden zwei Kategorien eingeteilt werden:
Compiler, wie der Compiler in Visual C++ 2005, die vorläufige OBJ-Dateien erstellen und anschließend mithilfe einer separaten Linkerphase die einzelnen Kompilierungseinheiten in einer einzigen Ziel-PE-Datei (Portable Executable-Datei) zusammenführen.
RAD-Tools (Rapid Application Development), die sämtlichen Code und sämtliche Datenstrukturen in der Toolumgebung bis zur Erstellungszeit verwalten, zu der sie in einem einzigen Schritt eine PE-Datei (Portable Executable-Datei) erstellen und ausgeben.
Andere Clients verwenden Metadaten-APIs möglicherweise auf eine Weise, die zwischen diesen beiden Methoden liegt. Einige Tools lassen möglicherweise das Metadatenmodul Optimierungen ausführen, sind aber an Informationen über die Neuzuordnung von Token nicht interessiert. Oder sie sind nur an den Informationen über die Neuzuordnung von einigen, aber nicht von allen Tokentypen interessiert. Tatsächlich ist es möglich, dass ein Compiler selbst bei Ausgabe einer OBJ-Datei keine Optimierungen durchführt.
Kompilierungs- und Verknüpfungsformat
Beim Kompilierungs- und Verknüpfungsinteraktionsformat erstellt ein Compiler-Front-End mithilfe der IMetaDataDispenserEx-API einen speicherresidenten Metadatenbereich und deklariert dann mithilfe der IMetaDataEmit-API Typen und Member. Dabei werden die in Übersicht über Metadatentoken beschriebenen Metadatenabstraktionen verwendet. Das Front-End kann jedoch keine Informationen über die Implementierung von Methoden (z. B. Informationen darüber, ob die Implementierung verwaltet oder nicht verwaltet ist, ob es sich um MSIL- oder prozessorspezifischen Code handelt) oder über relative virtuelle Adressen (RVA) bereitstellen, da diese Informationen zur Kompilierungszeit noch nicht verfügbar sind. Stattdessen müssen das Back-End oder der Linker diese Informationen zu einem späteren Zeitpunkt bereitstellen, wenn der Code kompiliert und in eine PE-Datei ausgegeben wird.
Eine Komplikation ergibt sich dadurch, dass das Back-End-Tool in der Lage sein muss, Informationen zur Zielspeichergröße der Metadatenbinärdatei abzurufen, damit Platz für diese Datei in der PE-Datei gelassen werden kann. Das Tool kann die Metadatenbinärdatei jedoch erst dann in der Datei speichern, wenn die RVAs der Methoden und die RVAs der statischen Datenmember auf Modulebene bekannt sind und in Metadaten ausgegeben wurden. Für die korrekte Berechnung der Zielspeichergröße muss das Metadatenmodul zunächst alle Optimierungen durchführen, die vor der Speicherung möglich sind, da diese Optimierungen die Größe der Zielbinärdatei im Idealfall reduzieren. Die Optimierungen können eine Sortierung der Datenstrukturen für schnellere Suchen umfassen oder eine Optimierung (frühes Binden) der mdTypeRef-Token und der mdMemberRef-Token, wenn der Verweis auf einen Typ oder Member vorliegt, der im aktuellen Bereich deklariert ist. Diese Sortierungen der Optimierungen können eine Neuzuordnung der Metadatentoken zur Folge haben, die das Tool wiederverwenden können muss, um die Implementierung und die RVA-Informationen auszugeben. Daher müssen Tool und Metadatenmodul zusammenarbeiten, um die Neuzuordnungen der Token zu verfolgen.
Die Reihenfolge der Aufrufe zum Beibehalten der Metadaten während der Kompilierung lautet daher wie folgt:
IMetaDataEmit::SetHandler, um eine IUnknown-Schnittstelle bereitzustellen, mit der das Metadatenmodul eine Abfrage nach IID_IMapToken durchführen kann, das den Client über Neuzuordnungen von Token benachrichtigt. SetHandler kann jederzeit nach Erstellen des Metadatenbereiches, muss aber vor IMetaDataEmit::GetSaveSize aufgerufen werden.
IMetaDataEmit::GetSaveSize, um die Speichergröße der Metadatenbinärdatei abzurufen. GetSaveSize verwendet die IMapToken-Schnittstelle, die in IMetaDataEmit::SetHandler bereitgestellt wird, um den Client über Neuzuordnungen von Token zu benachrichtigen. Wenn SetHandler nicht für die Bereitstellung einer IMapToken-Schnittstelle verwendet wurde, werden keine Optimierungen durchgeführt. Dadurch kann ein Compiler, der eine vorläufige OBJ-Datei ausgibt, unnötige Optimierungen überspringen, die voraussichtlich nach der Verknüpfungs- und Zusammenführungsphase erneut durchgeführt werden müssen.
IMetaDataEmit::Save, um die Metadatenbinärdatei beizubehalten, nachdem bei Bedarf IMetaDataEmit::SetRVA und andere IMetaDataEmit-Methoden zur Ausgabe der endgültigen Implementierungsmetadaten verwendet wurden.
Eine weitere Komplikation ergibt sich in der Linkerphase, wenn mehrere Kompilierungseinheiten in einer integrierten PE-Datei zusammengeführt werden. In diesem Fall müssen nicht nur die Metadatenbereiche zusammengeführt werden, auch die RVAs ändern sich bei Ausgabe der neuen PE-Datei erneut. In der Zusammenführungsphase ordnet die IMetaDataEmit::Merge-Methode, die für jeden Aufruf mit jeweils einem Importbereich und Ausgabebereich arbeitet, die Metadatentoken aus dem Importbereich im Ausgabebereich neu zu. Im Zusammenführungsprozess können außerdem Fehler auftreten, die nicht zu einem Programmabbruch führen und die der Prozess an den Client melden können muss. Nach Abschluss der Zusammenführung umfasst die Ausgabe der endgültigen PE-Datei auch einen Aufruf von IMetaDataEmit::GetSaveSize und eine weitere Neuzuordnung der Token.
Die Reihenfolge der Aufrufe zum Ausgeben und Beibehalten der Metadaten durch den Linker lautet wie folgt:
IMetaDataEmit::SetHandler, um eine IUnknown-Schnittstelle bereitzustellen, mit der das Metadatenmodul nicht nur, wie bisher, eine Abfrage nach IID_IMapToken, sondern auch nach IID_IMetaDataError durchführen kann. Mit der letztgenannten Schnittstelle wird der Client über Fehler benachrichtigt, die nicht zu einem Programmabbruch führen und die bei der Zusammenführung auftreten können.
IMetaDataEmit::Merge, um einen angegebenen Metadatenbereich mit dem aktuellen Ausgabebereich zusammenzuführen. Merge verwendet die IMapToken-Schnittstelle, um den Client über Neuzuordnungen von Token zu benachrichtigen, und IMetaDataError, um den Client über Fehler zu benachrichtigen, die nicht zu einem Programmabbruch führen.
IMetaDataEmit::GetSaveSize, um die Speichergröße der Metadatenbinärdatei abzurufen. GetSaveSize verwendet die IMapToken-Schnittstelle, die in IMetaDataEmit::SetHandler bereitgestellt wird, um den Client über Neuzuordnungen von Token zu benachrichtigen. Ein Tool muss auf die Behandlung der Neuzuordnungen von Token in Merge und erneut in GetSaveSize nach Durchführung der Formatoptimierungen vorbereitet sein. Die letzte Benachrichtigung für ein Token stellt die endgültige Zuordnung dar, auf die sich das Tool verlassen sollte.
IMetaDataEmit::Save, um die Metadatenbinärdatei beizubehalten, nachdem bei Bedarf IMetaDataEmit::SetRVA und andere IMetaDataEmit-Methoden zur Ausgabe der endgültigen Implementierungsmetadaten verwendet wurden.
RAD-Tool-Format
Wie beim Kompilierungs- und Verknüpfungsinteraktionsformat erstellt ein RAD-Tool mithilfe der IMetaDataDispenserEx-Schnittstelle einen speicherresidenten Metadatenbereich und deklariert dann mithilfe der IMetaDataEmit-Schnittstelle Typen und Member. Dabei werden die in Übersicht über Metadatentoken beschriebenen Metadatenabstraktionen verwendet.
Im Gegensatz zum Kompilierungs- und Verknüpfungsformat gibt das RAD-Tool die PE-Datei in der Regel in einem einzigen Schritt aus. Die Deklarations- und Implementierungsinformationen werden wahrscheinlich in einer einzigen Übergabe ausgegeben, und IMetaDataEmit::Merge muss vermutlich gar nicht aufgerufen werden. Der einzige Grund, warum das RAD-Tool möglicherweise die komplexe Neuzuordnung von Token behandeln muss, besteht daher darin, die Vorteile der Optimierungen vor der Speicherung zu nutzen, die derzeit von IMetaDataEmit::GetSaveSize durchgeführt werden.
Im Allgemeinen benötigt ein Tool, das vollständig optimierte Metadaten ausgeben kann, nicht das Metadatenmodul für die Ausgabe einer verhältnismäßig optimierten Datei. Aber zukünftige Implementierungen von Metadatenmodul und Dateiformat könnten in einigen Fällen zu veralteten Optimierungsstrategien führen, und so liegt ein klarer Regelsatz für die Ausgabe optimierter Metadaten vor.
Nach Ausgabe der Metadatendeklarationen und der Implementierungsinformationen lautet die Reihenfolge der Aufrufe wie folgt:
IMetaDataEmit::SetRVA und andere IMetaDataEmit-Methoden nach Bedarf, um die endgültigen Implementierungsmetadaten auszugeben.
IMetaDataEmit::Save, um die Metadatenbinärdatei beizubehalten.