11

WinMD 是一个二进制元数据文件,其中包含您需要了解的有关本地 WinRT dll 中可用的命名空间、类型、类、方法和参数的所有信息。

Windows 运行时设计

Windows 运行时使用 API 元数据(.winmd 文件)公开。这与 .NET 框架 (Ecma-335) 使用的格式相同。底层二进制合约使您可以轻松地以您选择的开发语言直接访问 Windows 运行时 API。

每个 .winmd 文件都公开一个或多个命名空间。这些命名空间按它们提供的功能分组。命名空间包含类、结构和枚举等类型。

伟大的; 我如何访问它?

Winmd 是 COM

引擎盖下的 WinRT 仍然是 COM。WinRT 中的 Winmd(Windows 元数据)是来自 COM 的旧 TLB(类型库)文件的现代版本。

| 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 tlb 读取元数据

给定一个 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 元数据程序集。这就是打包文件格式的技术细节。

生产 winmd 的好处之一是,因为它是常规的,我们现在可以构建工具来对 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”接口之一的方法添加元数据。

将重点放在“导入”“发出”这两个词上的作者可能试图提供一个线索——而不是直接给出答案。

奖金喋喋不休

  • 我不知道其中的名称空间或类型winmd(这就是我们要弄清楚的)
  • 使用 WinRT,我没有在 CLR 中运行托管代码;这是本机代码

我们可以用于这个问题的假设动机是我们将为一种还没有的语言创建一个投影(例如 ada、bpl、b、c)。另一个假设的动机是允许 IDE 能够显示 winmd 文件的元数据内容。

另外,请记住 WinRT 与 .NET 没有任何关系。

  • 它不是托管代码。
  • 它不存在于程序集中。
  • 它不在 .NET 运行时内运行。
  • 但是由于 .NET 已经为您提供了一种与 COM 互操作的方法(并且鉴于 WinRTCOM)
  • 您可以从托管代码中调用 WinRT 类

许多人似乎认为 WinRT 是 .NET 的另一个名称。WinRT 不使用、不需要或在 .NET、C#、.NET 框架或 .NET 运行时中运行。

  • WinRT 是本机代码
  • 因为 .NET Framework 类库是托管代码

WinRT 是本机代码的类库。.NET 人已经有了自己的类库。

奖金问题

本机 mscore 中有哪些函数可以让您处理 ECMA-335 二进制文件的元数据?

奖金阅读

4

2 回答 2

5

一个问题是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. 现在对于 winmd 中的每个typeID,您可以获得各种属性:

     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”
    • 祖先类型ID:例如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--> 枚举
    • 班级

宝贵的帮助来自:

我认为唯一的文档是使用 Microsoft 的 API 从 EMCA-335 程序集中读取元数据。

于 2019-02-06T20:11:29.210 回答
0

.winmd 文件遵循 ECMA-335 标准,因此任何能够读取 .NET 程序集的代码都可以读取 .winmd 文件。

我个人使用的两个选项是Mono.CecilSystem.Reflection.Metadata。我个人发现 Mono.Cecil 更容易使用。

于 2019-01-26T05:43:55.870 回答