Как прочитать winmd (файл метаданных WinRT)?
WinMD - это двоичный файл медаданных, который содержит все, что вам нужно для изучения пространств имен, типов, классов, методов, параметров, доступных в нативной WinRT dll.
Среда выполнения Windows предоставляется с использованием метаданных API (файлы.winmd). Это тот же формат, который используется в.NET Framework (Ecma-335). Базовый бинарный контракт упрощает доступ к API среды выполнения Windows непосредственно на выбранном вами языке разработки.
Каждый файл.winmd предоставляет одно или несколько пространств имен. Эти пространства имен сгруппированы по функциональности, которую они предоставляют. Пространство имен содержит типы, такие как классы, структуры и перечисления.
Большой; как мне получить к нему доступ?
Winmd это COM
WinRT под капотом все еще COM. И Winmd (Windows Metadata) в WinRT, это современная версия старых файлов TLB (библиотеки типов) из COM.
| COM | WinRT |
|----------------------------|--------------------------------|
| CoInitialize | RoInitialize |
| CoCreateInstance(ProgID)¹ | RoActivateInstance(ClassName) |
| *.tlb | *.winmd |
| compiled from idl | compiled from idl |
| HKCR\Classes\[ProgID] | HKLM\Software\Microsoft\WindowsRuntime\ActivatableClassId\[ClassName] |
| Code stored in native dll | Code stored in native dll |
| DllGetClassObject | DllGetClassObject |
| Is native code | Is native code |
| IUnknown | IUnknown (and IInspectible) |
| stdcall calling convention | stdcall calling convention |
| Everything returns HRESULT | Everything returns HRESULT |
| LoadTypeLib(*.tlb) | ???(*.winmd) |
Чтение метаданных из COM-файла
Имеется файл COM tlb (например, stdole.tlb
), вы можете использовать различные функции Windows для анализа tlb и получения из него информации.
Вызов LoadTypeLib дает вам ITypeLib
интерфейс:
ITypeLib tlb = LoadTypeLib("c:\Windows\system32\stdole2.tlb");
И тогда вы можете начать перебирать все в библиотеке типов
for (int i = 0 to tlb.GetTypeInfoCount-1)
{
ITypeInfo typeInfo = tlb.GetTypeInfo(i);
TYPEATTR typeAttr = typeInfo.GetTypeAttr();
case typeAttr.typeKind of
TKIND_ENUM: LoadEnum(typeINfo, typeAttr);
TKIND_DISPATCH,
TKIND_INTERFACE: LoadInterface(typeInfo, typeAttr);
TKIND_COCLASS: LoadCoClass(typeInfo, typeAttr);
else
//Unknown
end;
typeInfo.ReleaseTypeAttr(typeAttr);
}
Как мы делаем то же самое с *.winmd
файлы в мире WinRT?
От Ларри Остермана:
Из файлов idl мы создаем файл winmd. Файл winmd - это каноническое определение типа. И это то, что передается на языковые прогнозы. Языковые проекции читают файлы winmd, и они знают, как взять содержимое этого файла winmd - который является двоичным файлом - и затем спроецировать его и создать соответствующие языковые конструкции для этого языка.
Все они читают этот файл winmd. Это сборка только для метаданных ECMA-335. Это техническая деталь формата файла упаковки.
Одна из приятных вещей в создании winmds, потому что это регулярно, теперь мы можем создавать инструменты для сортировки, сопоставления, объединения методов и типов в файле winmd.
Загрузка метаданных из winmd
Я пытался использовать RoGetMetaDataFile
загрузить WinMD. Но RoGetMetaDataFile не предназначен для прямой обработки файла winmd. Он предназначен для того, чтобы вы могли найти информацию о типе, который, как вы уже знаете, существует, и вы знаете его имя.
Вызов RoGetMetadataFile завершится неудачно, если вы передадите ему winmd
имя файла:
HSTRING name = CreateWindowsString("C:\Windows\System32\WinMetadata\Windows.Globalization.winmd");
IMetaDataImport2 mdImport;
mdTypeDef mdType;
HRESULT hr = RoGetMetadataFile(name, null, null, out mdImport, out mdType);
0x80073D54
The process has no package identity
Что соответствует коду ошибки AppModel:
#define APPMODEL_ERROR_NO_PACKAGE 15700L
Но RoGetMetadataFile будет успешным, если вы передадите класс:
RoGetMetadataFile("Windows.Globalization.Calendar", ...);
Диспенсер метаданных
Было предложено использовать MetaDataGetDispenser для создания IMetaDataDispenser.
IMetaDataDispenser dispenser;
MetaDataGetDispenser(CLSID_CorMetaDataDispenser, IMetaDataDispenser, out dispenser);
Предположительно, вы можете использовать метод OpenScope, чтобы открыть winmd
файл:
Открывает существующий файл на диске и отображает его метаданные в память.
Файл должен содержать метаданные CLR.
Где первый параметр (Scope
) "Имя файла, который будет открыт."
Итак, мы стараемся:
IUnknown unk;
dispenser.OpenScope(name, ofRead, IID_?????, out unk);
За исключением того, что я не знаю, какой интерфейс я должен просить; документация не скажет. Это замечание:
Копия метаданных в памяти может быть запрошена с использованием методов из одного из интерфейсов "import" или добавлена с использованием методов из одного из интерфейсов "emit".
Автор, который делает акцент на словах "import" и "emit", вероятно, пытается дать подсказку - без откровенного ответа.
Бонус Болтовня
- я не знаю пространства имен или типы в
winmd
(это то, что мы пытаемся выяснить) - с WinRT я не запускаю управляемый код внутри CLR; это для нативного кода
Гипотетическая мотивация, которую мы можем использовать в этом вопросе, заключается в том, что мы собираемся создать проекцию для языка, у которого его еще нет (например, ada, bpl, b, c). Другая гипотетическая мотивация - позволить IDE отображать содержимое метаданных файла winmd.
Также помните, что WinRT никак не связан с.NET.
- Это не управляемый код.
- Он не существует в сборке.
- Он не работает внутри среды выполнения.NET.
- Но так как.NET уже предоставляет вам способ взаимодействия с COM (и учитывая, что WinRT является COM)
- вы можете вызывать классы WinRT из своего управляемого кода
Кажется, многие думают, что WinRT - это еще одно название для.NET. WinRT не использует, не требует и не работает в.NET, C#,.NET Framework или.NET Runtime.
- WinRT это нативный код
- как библиотека классов.NET Framework для управляемого кода
WinRT - это библиотека классов для нативного кода. У людей.NET уже есть своя собственная библиотека классов.
Бонусный вопрос
Какие функции в нативном mscore позволяют обрабатывать метаданные двоичного файла ECMA-335?
Бонус Чтение
3 ответа
Одна проблема состоит в том, что есть два комплекта документации для IMetadataDispsenser.OpenScope:
- IMetaDataDispenser:: метод OpenScope в документации рабочего стола Windows Runtime
- IMetaDataDispenser:: Метод OpenScope в неуправляемой справочной документации по.NET Framework
И хотя документация по среде выполнения Windows не предлагает никакой документации:
riid
IID желаемого интерфейса метаданных, который должен быть возвращен; вызывающая сторона будет использовать интерфейс для импорта (чтения) или передачи (записи) метаданных.
Версия.NET Framework предлагает документацию:
riid
[in] IID желаемого интерфейса метаданных, который должен быть возвращен; вызывающая сторона будет использовать интерфейс для импорта (чтения) или передачи (записи) метаданных.
Значение riid должно указывать один из интерфейсов "import" или "emit". Допустимые значения:
- IID_IMetaDataImport
- IID_IMetaDataImport2
- IID_IMetaDataAssemblyImport
- IID_IMetaDataEmit
- IID_IMetaDataEmit2
- IID_IMetaDataAssemblyEmit
Так что теперь мы можем начать все вместе.
Создайте свой распределитель метаданных:
IMetadataDispsener dispener; MetaDataGetDispenser(CLSID_CorMetaDataDispenser, IMetaDataDispenser, out dispenser);
Используйте OpenScope, чтобы указать
*.winmd
файл, который вы хотите прочитать. Мы запрашиваем интерфейс IMetadataImport, потому что мы хотим импортировать данные из winmd (а не экспортировать их в winmd)://Open the winmd file we want to dump String filename = "C:\Windows\System32\WinMetadata\Windows.Globalization.winmd"; IMetaDataImport reader; //IMetadataImport2 supports generics dispenser.OpenScope(filename, ofRead, IMetaDataImport, out reader); //"Import" is used to read metadata. "Emit" is used to write metadata.
Когда у вас есть импортер метаданных, вы можете начать перечислять все типы в файле метаданных:
Pointer enum = null; mdTypeDef typeID; Int32 nRead; while (reader.EnumTypeDefs(enum, out typeID, 1, out nRead) = S_OK) { ProcessToken(reader, typeID); } reader.CloseEnum(enum);
И теперь для каждого typeID в winmd вы можете получить различные свойства:
void ProcessToken(IMetaDataImport reader, mdTypeDef typeID) { //Get three interesting properties of the token: String typeName; //e.g. "Windows.Globalization.NumberFormatting.DecimalFormatter" UInt32 ancestorTypeID; //the token of this type's ancestor (e.g. Object, Interface, System.ValueType, System.Enum) CorTypeAttr flags; //various flags about the type (e.g. public, private, is an interface) GetTypeInfo(reader, typeID, out typeName, out ancestorTypeID, out flags); }
И есть некоторая хитрость, необходимая при получении информации о типе:
- если тип определен в самой winmd: используйте GetTypeDefProps
- если тип является "ссылкой" на тип, который существует в другом winmd: используйте GetTypeRefProps
Единственный способ определить разницу - это попытаться прочитать свойства типа, предполагая, что это определение типа с использованием GetTypeDefProps, и проверить возвращаемое значение:
- если он вернется
S_OK
это ссылка на тип если он вернется
S_FALSE
это определение типаПолучить свойства типа, в том числе:
- typeName: например, "Windows.Globalization.NumberFormatting.DecimalFormatter"
- ancestorTypeID: например, 0x10000004
- флаги: например, 0x00004101
void GetTypeInf(IMetaDataImport reader, mdTypeDef typeID, out String typeName, DWORD ancestorTypeID, CorTypeAttr flags) { DWORD nRead; DWORD tdFlags; DWORD baseClassToken; hr = reader.GetTypeDefProps(typeID, null, 0, out nRead, out tdFlags, out baseClassToken); if (hr == S_OK) { //Allocate buffer for name SetLength(typeName, nRead); reader.GetTypeDefProps(typeID, typeName, Length(typeName), out nRead, out flags, out ancestorTypeID); return; } //We couldn't find it a a type **definition**. //Try again as a type **reference** hr = reader.GetTypeRefProps(typeID, null, 0, out nRead, out tdFlags, out baseClassToken); if (hr == S_OK) { //Allocate buffer for name SetLength(typeName, nRead); reader.GetTypeRefProps(typeID, typeName, Length(typeName), out nRead, out flags, out ancestorTypeID); return; } }
Есть и другие интересные ошибки, если вы пытаетесь расшифровать типы. В среде выполнения Windows все либо принципиально:
- интерфейс
- или класс
Структуры и перечисления также являются классами; но потомок определенного класса:
- интерфейс
- учебный класс
System.ValueType
-> структураSystem.Enum
-> enum- учебный класс
Бесценная помощь поступила от:
я считаю, что единственная документация - это существование при чтении метаданных из сборки EMCA-335 с использованием API Microsoft.
Я собираюсь дать ответ из комментариев, так как это помогло мне:
Чтобы просмотреть файл WinMD в графическом интерфейсе
Если вы хотите просмотреть файл WinMD (т. е. взаимодействовать с ним в графическом интерфейсе, визуально видеть компоненты файла), вы можете использовать ildasm.exe, входящий в состав Visual Studio, для его просмотра.
Вы также можете использовать ILSpy (с открытым исходным кодом) и, возможно, .NET Reflector (платный).
(через user3267386 и user12597 )Файлы.winmd соответствуют стандарту ECMA-335, поэтому любой код, способный читать сборки.NET, может читать файлы.winmd.
Лично я использовал два варианта: Mono.Cecil и https://www.nuget.org/packages/System.Reflection.Metadata/. Я лично нашел, что с Mono.Cecil легче работать.