com - 如何读取 winmd(WinRT 元数据文件)?
问题描述
WinMD 是一个二进制元数据文件,其中包含您需要了解的有关本地 WinRT dll 中可用的命名空间、类型、类、方法和参数的所有信息。
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 互操作的方法(并且鉴于 WinRT是COM)
- 您可以从托管代码中调用 WinRT 类
许多人似乎认为 WinRT 是 .NET 的另一个名称。WinRT 不使用、不需要或在 .NET、C#、.NET 框架或 .NET 运行时中运行。
- WinRT 是本机代码
- 因为 .NET Framework 类库是托管代码
WinRT 是本机代码的类库。.NET 人已经有了自己的类库。
奖金问题
本机 mscore 中有哪些函数可以让您处理 ECMA-335 二进制文件的元数据?
奖金阅读
解决方案
一个问题是IMetadataDispsenser.OpenScope有两组文档:
- 桌面 Windows 运行时文档中的IMetaDataDispenser::OpenScope 方法
- .NET Framework 非托管参考文档中的IMetaDataDispenser::OpenScope 方法
虽然 Windows 运行时文档没有提供任何文档:
riid
要返回的所需元数据接口的 IID;调用者将使用该接口来导入(读取)或发出(写入)元数据。
.NET Framework 版本确实提供了文档:
riid
[in] 要返回的所需元数据接口的 IID;调用者将使用该接口来导入(读取)或发出(写入)元数据。
riid 的值必须指定“import”或“emit”接口之一。有效值为:
- IID_IMetaDataImport
- IID_IMetaDataImport2
- IID_IMetaDataAssemblyImport
- IID_IMetaDataEmit
- IID_IMetaDataEmit2
- IID_IMetaDataAssemblyEmit
所以现在我们可以开始把所有东西放在一起。
创建您的元数据分配器:
IMetadataDispsener dispener; MetaDataGetDispenser(CLSID_CorMetaDataDispenser, IMetaDataDispenser, out dispenser);
使用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.
一旦有了元数据导入器,就可以开始枚举元数据文件中的所有类型:
Pointer enum = null; mdTypeDef typeID; Int32 nRead; while (reader.EnumTypeDefs(enum, out typeID, 1, out nRead) = S_OK) { ProcessToken(reader, typeID); } reader.CloseEnum(enum);
现在对于 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
它是一个类型定义
获取类型的属性,包括:
- 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
--> 枚举- 班级
宝贵的帮助来自:
- C++ .Net 16 ( archive.is )的早期阶段
我认为唯一的文档是使用 Microsoft 的 API 从 EMCA-335 程序集中读取元数据。
推荐阅读
- mysql - 优化 mySQL 查询以更好地扩展
- aws-glue - Athena 没有看到新的每日分区文件夹
- image - 在没有 ui 的情况下在 Flutter 中裁剪和保存图像
- caching - Varnish Cache 4.1 行为怪异 - 缓存我的登录页面一次然后不再缓存 - VCL 代码
- mysql - 如何使用 LAG 从 MYSQL 表中获取最后一个非空值
- angular - Angular - 将应用名称添加到您的 Angular 应用 url
- javascript - 如何在所有 Vue3 组件中设置反应式语言环境?
- powershell - 每当我在项目文件夹中启动 Powershell 会话时,如何激活 conda 环境?
- sql-server - 如何使用 BCP 安全迁移数据并使用 SQL Server 2008 格式化文件?
- google-sheets - Google Sheets:基于 Master Sheet 自动在多个工作表中添加行