Как прочитать winmd (файл метаданных WinRT)? - PullRequest
0 голосов
/ 26 января 2019

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

С Дизайн среды выполнения Windows :

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

Чтение бонусов

Ответы [ 2 ]

0 голосов
/ 06 февраля 2019

Одна проблема состоит в том, что есть два набора документации для IMetadataDispsenser.OpenScope :

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

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: use GetTypeRefProps

Единственный способ определить разницу - это попытаться прочитать свойства типа, предполагая, что это тип определение , используя GetTypeDefProps и проверить возвращаемое значение:

  • если возвращается S_OK это тип ссылка
  • если возвращается S_FALSE это тип определение

  • Получите свойства типа, включая:

    • typeName: например, "Windows.Globalization.NumberFormatting.DecimalFormatter"
    • ancestorTypeID: например, 0x10000004
    • флаги: например, 0x00004101

Есть и другие интересные ошибки, если вы пытаетесь расшифровать типы.В среде выполнения Windows все либо принципиально:

  • интерфейс
  • или класс

Структуры и перечисления также являются классами;но потомок определенного класса:

  • interface
  • class
    • System.ValueType -> struct
    • System.Enum -> enum
    • класс

Бесценная помощь поступила от:

, который, как я считаю, является единственной документацией, существующей при чтении метаданных из сборки EMCA-335 с использованием Microsoft API.

0 голосов
/ 26 января 2019

.winmd файлы соответствуют стандарту ECMA-335, поэтому любой код, способный читать сборки .NET, может читать .winmd файлы.

Лично я использовал два варианта: Mono.Cecil и System.Reflection.Metadata . Я лично нашел, что с Mono.Cecil легче работать.

...