【问题标题】:How to read a winmd (WinRT metadata file)?如何读取 winmd(WinRT 元数据文件)?
【发布时间】:2019-01-26 04:57:31
【问题描述】:

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 文件是类型的规范 定义。这就是传递给语言预测的内容。语言投影读取 winmd 文件,它们知道如何获取该 winmd 文件的内容 - 这是一个二进制文件 - 然后对其进行投影并为该语言生成适当的语言结构。

他们都阅读了那个 winmd 文件。它恰好是一个 ECMA-335 元数据程序集。这就是打包文件格式的技术细节。

生产 winmd 的好处之一是,因为它是常规的,我们现在可以构建工具来对 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", ...);

元数据分配器

有人建议使用 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” 这两个词上的作者可能试图提供一个线索 - 而不是直接给出答案。 p>

奖金聊天

  • 我不知道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 二进制文件的元数据?

阅读奖励

【问题讨论】:

  • xlang 项目包括一个 winmd 阅读器。 Sample program to convert a winmd back to MIDL3Guts of the implementation。听起来 xlang 项目正在做您感兴趣的事情:向新语言添加投影。 (我认为他们现在正在研究 python。)
  • 或者您可以使用 MetadataGetDispenser 打开 winmd 文件并请求 IMetaDataImport,然后调用各种 EnumXxx 方法进行窥探。
  • 这不是一个直接的答案,但您至少可以查看 ILDasm 中随 Visual 分发的 .winmd 文件的内容工作室。
  • ILSpy 也可以查看它们。 .NET Reflector 可能也可以,但我现在不能肯定它是付费商业软件。

标签: com windows-runtime midl winmd


【解决方案1】:

一个问题是 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

区分的唯一方法是尝试读取类型属性 假设它是一个类型 definition 使用 GetTypeDefProps 并检查返回值:

  • 如果它返回S_OK 它是一个类型引用
  • 如果它返回S_FALSE 它是一个类型定义

 

  1. 获取类型的属性,包括:

    • 类型名称:例如"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 程序集中读取元数据。

【讨论】:

  • 关于 GetTypeDefProps,上面说“如果它返回 S_OK 它是一个类型引用”“如果它返回 S_FALSE 它是一个类型定义”,但常识和示例代码似乎表明它确实存在另一种方式。
  • token值的高字节表示token的类型(来源:bytepointer.com/resources/pietrek_dotnet_metadata_dll_hell.htm和SDK corhdr.h),所以你可以检查以区分mdTypeDef和mdTypeRef值(无需尝试GetTypeDefProps 和 GetTypeRefProps 顺序如上所示)。尽管我认为 API 不会像上面暗示的那样将它们混合在一起;即你只能从 EnumTypeDefs 获得 mdTypeDef,并且只能从 EnumTypeRefs 获得 mdTypeRef。
【解决方案2】:

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

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

【讨论】:

  • 不幸的是,我认为这两个都只在 CLR 中运行。
  • 你需要用 C++ 来做吗?
猜你喜欢
  • 2012-09-24
  • 1970-01-01
  • 2016-02-06
  • 1970-01-01
  • 1970-01-01
  • 2019-07-13
  • 1970-01-01
  • 2017-06-30
  • 1970-01-01
相关资源
最近更新 更多