如何读取winmd(WinRT元数据文件)?

时间:2019-01-26 04:57:32

标签: com windows-runtime midl winmd

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

来自 Windows Runtime design

  

使用API​​元数据(.winmd文件)公开Windows运行时。这是.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);
}

我们如何处理WinRT世界中的*.winmd文件?

来自拉里·奥斯特曼:

  

从idl文件中,我们生成一个winmd文件。 Winmd文件是该类型的 canonical 定义。这就是语言预测的基础。语言投影读取了winmd文件,并且他们知道如何获取该winmd文件(它是一个二进制文件)的内容,然后进行投影并为该语言生成适当的语言构造。

     

他们都读取了winmd文件。碰巧是仅ECMA-335元数据的程序集。这就是打包文件格式的技术细节。

     

关于生成winmds的一件好事,因为它是 regular ,我们现在可以构建工具来对winmd文件中的方法和类型进行排序,整理,合并。

从winmd加载元数据

我尝试使用RoGetMetaDataFile加载WinMD。但是 RoGetMetaDataFile 并不意味着可以直接处理winmd文件。它旨在让您发现有关已经知道的类型的信息-并且知道其名称。

如果传递winmd文件名,则调用 RoGetMetadataFile 失败:

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", ...);

MetaData分配器

有人建议使用 MetaDataGetDispenser 创建 IMetaDataDispenser

IMetaDataDispenser dispenser;
MetaDataGetDispenser(CLSID_CorMetaDataDispenser, IMetaDataDispenser, out dispenser);

大概是,您可以使用 OpenScope 方法打开一个winmd文件:

  

打开一个现有的磁盘文件,并将其元数据映射到内存中。
  该文件必须包含公共语言运行时(CLR)元数据。

第一个参数(Scope)是“要打开的文件的名称。”

所以我们尝试:

IUnknown unk;
dispenser.OpenScope(name, ofRead, IID_?????, out unk);

除了我不知道我该要求什么接口;文档不会说。它确实指出:

  

可以使用“导入”界面之一的方法查询元数据的内存中副本,也可以使用“发射”界面之一的方法将其添加到元数据中。

着重强调“进口” “散发” 的作者可能正试图提供一个线索-不能完全放弃答案。

>

奖励聊天

  • 我不知道winmd中的名称空间或类型(这就是我们要找出的内容)
  • 使用WinRT时,我不在CLR中运行托管代码;这是针对本机代码的

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

另外,请记住WinRT与.NET毫无关系。

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

许多人似乎认为WinRT是.NET的别称。 WinRT不会使用,要求或在.NET,C#、. NET框架或.NET运行时中运行。

  • WinRT适用于本机代码
  • .NET Framework类库用于托管代码

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

奖金问题

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

奖金阅读

2 个答案:

答案 0 :(得分:0)

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

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

答案 1 :(得分:0)

一个问题是 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,则为 reference
  • 类型
  • 如果返回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->枚举
    • 课程

宝贵的援助来自:

我相信是唯一的文档,其中存在有关使用Microsoft API从EMCA-335组件读取元数据的情况。