如何读取 winmd(WinRT 元数据文件)?
How to read a winmd (WinRT metadata file)?
WinMD 是一个二进制元数据文件,其中包含您需要了解的有关名称空间、类型、classes、方法、本机 WinRT dll 中可用参数的所有信息。
The Windows Runtime is exposed using API metadata (.winmd files). This is the same format used by the .NET framework (Ecma-335). The underlying binary contract makes it easy for you to access the Windows Runtime APIs directly in the development language of your choice.
Each .winmd file exposes one or more namespaces. These namespaces are grouped by the functionality that they provide. A namespace contains types such as classes, structures, and enumerations.
很好;我如何访问它?
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
个文件执行相同的操作?
来自拉里·奥斯特曼:
From the idl files we produce a winmd file. A winmd file is the canonical definition of the type. And that's what get handed off to the language projections. The language projections read the winmd files, and they know how to take the contents of that winmd file - which is a binary file - and then project that and produce the appropriate language constructs for that language.
They all read that winmd file. It happens to be an ECMA-335 metadata-only assembly. That's the technical detail of the packaging file format.
One of the nice things about producing winmds, because it's regular, we can now build tooling to sort, collate, combine, the methods and types in a winmd file.
正在从 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 如果您传递 class:
确实会成功
RoGetMetadataFile("Windows.Globalization.Calendar", ...);
元数据分配器
有人建议使用 MetaDataGetDispenser to create an IMetaDataDispenser。
IMetaDataDispenser dispenser;
MetaDataGetDispenser(CLSID_CorMetaDataDispenser, IMetaDataDispenser, out dispenser);
据推测你可以使用OpenScope方法打开一个winmd
文件:
Opens an existing, on-disk file and maps its metadata into memory.
The file must contain common language runtime (CLR) metadata.
其中第一个参数(Scope
)为"要打开的文件名。"
所以我们尝试:
IUnknown unk;
dispenser.OpenScope(name, ofRead, IID_?????, out unk);
除非我不知道我应该要求什么界面;文档不会说。它确实备注:
The in-memory copy of the metadata can be queried using methods from one of the "import" interfaces, or added to using methods from the one of the "emit" interfaces.
作者强调 “导入” 和 “发出” 可能是想提供一个线索 - 没有直接给出答案。
奖金聊天
- 我不知道
winmd
中的命名空间或类型(这正是我们要弄清楚的)
- 使用 WinRT 我不是 运行在 CLR 中管理代码;这是本机代码
我们可以使用这个问题的假设动机是,我们将为一种还没有投影的语言(例如 ada、bpl、b、c)创建一个投影。另一个假设的动机是允许 IDE 能够显示 winmd 文件的元数据内容。
此外,请记住 WinRT 与 .NET 没有任何关系。
- 它不是托管代码。
- 程序集中不存在。
- 它不会 运行 在 .NET 运行 时间内。
- 但是由于 .NET 已经为您提供了一种与 COM 互操作的方法(并且假设 WinRT 是 COM)
- 您可以从托管代码中调用 WinRT classes
很多人似乎认为 WinRT 是 .NET 的别称。 WinRT 不在 .NET、C#、.NET 框架或 .NET 运行time 中使用、要求或操作。
- WinRT 是本机代码
- 作为 .NET Framework Class 库用于托管代码
WinRT 是一个 class 本机代码库。 .NET 人员已经拥有自己的 class 库。
奖金问题
原生 mscore 中有哪些函数可以让您处理 ECMA-335 二进制文件的元数据?
红利阅读
.winmd 文件遵循 ECMA-335 标准,因此任何能够读取 .NET 程序集的代码都可以读取 .winmd 文件。
我个人使用的两个选项是 Mono.Cecil and System.Reflection.Metadata。我个人认为 Mono.Cecil 更容易使用。
一个问题是 IMetadataDispsenser.OpenScope:
有两套文档
- IMetaDataDispenser::OpenScope method 在桌面中 Windows 运行时文档
.NET Framework 非托管参考文档中的 - IMetaDataDispenser::OpenScope Method
虽然 Windows 运行时文档没有提供文档:
riid
The IID of the desired metadata interface to be returned; the caller will use the interface to import (read) or emit (write) metadata.
.NET Framework 版本 提供文档:
riid
[in] The IID of the desired metadata interface to be returned; the caller will use the interface to import (read) or emit (write) metadata.
The value of riid must specify one of the "import" or "emit" interfaces. Valid values are:
- IID_IMetaDataImport
- IID_IMetaDataImport2
- IID_IMetaDataAssemblyImport
- IID_IMetaDataEmit
- IID_IMetaDataEmit2
- IID_IMetaDataAssemblyEmit
所以现在我们可以开始把所有东西放在一起了。
创建您的元数据分配器:
IMetadataDispsener dispener;
MetaDataGetDispenser(CLSID_CorMetaDataDispenser, IMetaDataDispenser, out dispenser);
使用OpenScope指定要读取的*.winmd
文件。我们要求 IMetadataImport 接口,因为我们想从 winmd import 数据(而不是 export它到 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
区分的唯一方法是尝试读取类型属性
假设它是一个类型 definition 使用 GetTypeDefProps 并检查 return 值:
- 如果它 returns
S_OK
它是一个类型 reference
- 如果它 returns
S_FALSE
它是一个类型 定义
获取类型的属性,包括:
- 类型名称:例如"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 运行时,从根本上说,一切都是:
- 一个界面
- 或class
结构和枚举也是 class;但特定 class:
的后代
- 界面
- class
System.ValueType
--> 结构
System.Enum
--> 枚举
- class
宝贵的帮助来自:
我认为唯一的文档是使用 Microsoft 的 API.
从 EMCA-335 程序集读取元数据
WinMD 是一个二进制元数据文件,其中包含您需要了解的有关名称空间、类型、classes、方法、本机 WinRT dll 中可用参数的所有信息。
The Windows Runtime is exposed using API metadata (.winmd files). This is the same format used by the .NET framework (Ecma-335). The underlying binary contract makes it easy for you to access the Windows Runtime APIs directly in the development language of your choice.
Each .winmd file exposes one or more namespaces. These namespaces are grouped by the functionality that they provide. A namespace contains types such as classes, structures, and enumerations.
很好;我如何访问它?
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
个文件执行相同的操作?
来自拉里·奥斯特曼:
From the idl files we produce a winmd file. A winmd file is the canonical definition of the type. And that's what get handed off to the language projections. The language projections read the winmd files, and they know how to take the contents of that winmd file - which is a binary file - and then project that and produce the appropriate language constructs for that language.
They all read that winmd file. It happens to be an ECMA-335 metadata-only assembly. That's the technical detail of the packaging file format.
One of the nice things about producing winmds, because it's regular, we can now build tooling to sort, collate, combine, the methods and types in a winmd file.
正在从 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 如果您传递 class:
确实会成功RoGetMetadataFile("Windows.Globalization.Calendar", ...);
元数据分配器
有人建议使用 MetaDataGetDispenser to create an IMetaDataDispenser。
IMetaDataDispenser dispenser;
MetaDataGetDispenser(CLSID_CorMetaDataDispenser, IMetaDataDispenser, out dispenser);
据推测你可以使用OpenScope方法打开一个winmd
文件:
Opens an existing, on-disk file and maps its metadata into memory.
The file must contain common language runtime (CLR) metadata.
其中第一个参数(Scope
)为"要打开的文件名。"
所以我们尝试:
IUnknown unk;
dispenser.OpenScope(name, ofRead, IID_?????, out unk);
除非我不知道我应该要求什么界面;文档不会说。它确实备注:
The in-memory copy of the metadata can be queried using methods from one of the "import" interfaces, or added to using methods from the one of the "emit" interfaces.
作者强调 “导入” 和 “发出” 可能是想提供一个线索 - 没有直接给出答案。
奖金聊天
- 我不知道
winmd
中的命名空间或类型(这正是我们要弄清楚的) - 使用 WinRT 我不是 运行在 CLR 中管理代码;这是本机代码
我们可以使用这个问题的假设动机是,我们将为一种还没有投影的语言(例如 ada、bpl、b、c)创建一个投影。另一个假设的动机是允许 IDE 能够显示 winmd 文件的元数据内容。
此外,请记住 WinRT 与 .NET 没有任何关系。
- 它不是托管代码。
- 程序集中不存在。
- 它不会 运行 在 .NET 运行 时间内。
- 但是由于 .NET 已经为您提供了一种与 COM 互操作的方法(并且假设 WinRT 是 COM)
- 您可以从托管代码中调用 WinRT classes
很多人似乎认为 WinRT 是 .NET 的别称。 WinRT 不在 .NET、C#、.NET 框架或 .NET 运行time 中使用、要求或操作。
- WinRT 是本机代码
- 作为 .NET Framework Class 库用于托管代码
WinRT 是一个 class 本机代码库。 .NET 人员已经拥有自己的 class 库。
奖金问题
原生 mscore 中有哪些函数可以让您处理 ECMA-335 二进制文件的元数据?
红利阅读
.winmd 文件遵循 ECMA-335 标准,因此任何能够读取 .NET 程序集的代码都可以读取 .winmd 文件。
我个人使用的两个选项是 Mono.Cecil and System.Reflection.Metadata。我个人认为 Mono.Cecil 更容易使用。
一个问题是 IMetadataDispsenser.OpenScope:
有两套文档- IMetaDataDispenser::OpenScope method 在桌面中 Windows 运行时文档 .NET Framework 非托管参考文档中的
- IMetaDataDispenser::OpenScope Method
虽然 Windows 运行时文档没有提供文档:
riid
The IID of the desired metadata interface to be returned; the caller will use the interface to import (read) or emit (write) metadata.
.NET Framework 版本 提供文档:
riid
[in] The IID of the desired metadata interface to be returned; the caller will use the interface to import (read) or emit (write) metadata.
The value of riid must specify one of the "import" or "emit" interfaces. Valid values are:
- IID_IMetaDataImport
- IID_IMetaDataImport2
- IID_IMetaDataAssemblyImport
- IID_IMetaDataEmit
- IID_IMetaDataEmit2
- IID_IMetaDataAssemblyEmit
所以现在我们可以开始把所有东西放在一起了。
创建您的元数据分配器:
IMetadataDispsener dispener; MetaDataGetDispenser(CLSID_CorMetaDataDispenser, IMetaDataDispenser, out dispenser);
使用OpenScope指定要读取的
*.winmd
文件。我们要求 IMetadataImport 接口,因为我们想从 winmd import 数据(而不是 export它到 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
区分的唯一方法是尝试读取类型属性 假设它是一个类型 definition 使用 GetTypeDefProps 并检查 return 值:
- 如果它 returns
S_OK
它是一个类型 reference - 如果它 returns
S_FALSE
它是一个类型 定义
获取类型的属性,包括:
- 类型名称:例如"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 运行时,从根本上说,一切都是:
- 一个界面
- 或class
结构和枚举也是 class;但特定 class:
的后代- 界面
- class
System.ValueType
--> 结构System.Enum
--> 枚举- class
宝贵的帮助来自:
我认为唯一的文档是使用 Microsoft 的 API.
从 EMCA-335 程序集读取元数据