Как прочитать winmd (файл метаданных WinRT)?

WinMD - это двоичный файл медаданных, который содержит все, что вам нужно для изучения пространств имен, типов, классов, методов, параметров, доступных в нативной WinRT dll.

Из Windows Runtime design:

Среда выполнения 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:

И хотя документация по среде выполнения Windows не предлагает никакой документации:

riid

IID желаемого интерфейса метаданных, который должен быть возвращен; вызывающая сторона будет использовать интерфейс для импорта (чтения) или передачи (записи) метаданных.

Версия.NET Framework предлагает документацию:

riid

[in] IID желаемого интерфейса метаданных, который должен быть возвращен; вызывающая сторона будет использовать интерфейс для импорта (чтения) или передачи (записи) метаданных.

Значение riid должно указывать один из интерфейсов "import" или "emit". Допустимые значения:

  • IID_IMetaDataImport
  • IID_IMetaDataImport2
  • IID_IMetaDataAssemblyImport
  • IID_IMetaDataEmit
  • IID_IMetaDataEmit2
  • IID_IMetaDataAssemblyEmit

Так что теперь мы можем начать все вместе.


  1. Создайте свой распределитель метаданных:

    IMetadataDispsener dispener;
    MetaDataGetDispenser(CLSID_CorMetaDataDispenser, IMetaDataDispenser, out dispenser);
    
  2. Используйте 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.
    
  3. Когда у вас есть импортер метаданных, вы можете начать перечислять все типы в файле метаданных:

    Pointer enum = null;
    mdTypeDef typeID;
    Int32 nRead;
    while (reader.EnumTypeDefs(enum, out typeID, 1, out nRead) = S_OK)
    {
       ProcessToken(reader, typeID);
    }
    reader.CloseEnum(enum);
    
  4. И теперь для каждого 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 это определение типа

    1. Получить свойства типа, в том числе:

      • 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 легче работать.

Другие вопросы по тегам