【发布时间】:2019-01-26 04:57:31
【问题描述】:
WinMD 是一个二进制元数据文件,其中包含您需要了解的有关本地 WinRT dll 中可用的命名空间、类型、类、方法和参数的所有信息。
使用 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 MIDL3。 Guts of the implementation。听起来 xlang 项目正在做您感兴趣的事情:向新语言添加投影。 (我认为他们现在正在研究 python。)
-
或者您可以使用 MetadataGetDispenser 打开 winmd 文件并请求 IMetaDataImport,然后调用各种 EnumXxx 方法进行窥探。
-
这不是一个直接的答案,但您至少可以查看 ILDasm 中随 Visual 分发的 .winmd 文件的内容工作室。
-
ILSpy 也可以查看它们。 .NET Reflector 可能也可以,但我现在不能肯定它是付费商业软件。
标签: com windows-runtime midl winmd