A WinMD is a binary medadata file, that contains everything you need to learn about namespaces, types, classes, methods, parameters available in a native WinRT dll.
From Windows Runtime design:
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.
Great; how do I access it?
WinRT under the hood is still COM. And Winmd (Windows Metadata) in WinRT, is the modern version of the old TLB (type library) files from COM.
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 + IInspectable |
stdcall calling convention | stdcall calling convention |
Everything returns HRESULT |
Everything returns HRESULT |
LoadTypeLib(*.tlb) |
???(*.winmd) |
Given a COM tlb file (e.g. stdole.tlb
), you can use various Windows functions to parse the tlb to get information out of it.
A call to LoadTypeLib gets you an ITypeLib
interface:
ITypeLib tlb = LoadTypeLib("c:\Windows\system32\stdole2.tlb");
And then you can start iterating everything in the type library
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);
}
How do we do the same with *.winmd
files in the WinRT world?
From Larry Osterman:
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.
I've tried using RoGetMetaDataFile
to load a WinMD. But RoGetMetaDataFile is not meant to let you process a winmd file directly. It is meant to let you discover information about a type that you already know exists - and you know its name.
Calling RoGetMetadataFile fails if you pass it a winmd
filename:
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
Which corresponds to AppModel error code:
#define APPMODEL_ERROR_NO_PACKAGE 15700L
But RoGetMetadataFile does succeed if you pass a class:
RoGetMetadataFile("Windows.Globalization.Calendar", ...);
There was a suggestion to use MetaDataGetDispenser to create an IMetaDataDispenser.
IMetaDataDispenser dispenser;
MetaDataGetDispenser(CLSID_CorMetaDataDispenser, IMetaDataDispenser, out dispenser);
Presumably you can use the OpenScope method to open a winmd
file:
Opens an existing, on-disk file and maps its metadata into memory.
The file must contain common language runtime (CLR) metadata.
Where the first parameter (Scope
) is "The name of the file to be opened."
So we try:
IUnknown unk;
dispenser.OpenScope(name, ofRead, IID_?????, out unk);
Except i don't know what interface i'm supposed to be asking for; the documentation won't say. It does remark:
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.
The author who put the emphasis on the words "import" and "emit" is probably trying to provide a clue - without outright giving away the answer.
winmd
(that's what we're trying to figure out)The hypothetical motivation we can use for this question is that we're going to be creating a projection for a language that doesn't have one yet (e.g. ada, bpl, b, c). The other hypothetical motivation is to allow an IDE to be able to display metadata contents of a winmd file.
Also, remember that WinRT is not related to .NET in any way.
Many people seem to think WinRT is another name for .NET. WinRT does not use, require, or operate in .NET, C#, a .NET framework, or a .NET runtime.
WinRT is a class library for native code. .NET people already have their own class library.
What are the functions in native mscore that lets you process the metadata of an ECMA-335 binary file?
One problem is there is two sets of documentation for IMetadataDispsenser.OpenScope:
And while the Windows Runtime documentation offers no documentation:
riid
The IID of the desired metadata interface to be returned; the caller will use the interface to import (read) or emit (write) metadata.
The .NET Framework version does offer documentation:
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
So now we can start to put everything together.
Create your metadata dispenser:
IMetadataDispsener dispener;
MetaDataGetDispenser(CLSID_CorMetaDataDispenser, IMetaDataDispenser, out dispenser);
Use OpenScope to specify the .winmd
file you want to read. We ask for the IMetadataImport interface, because we want to import data from a winmd (rather than export it to a .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.
Once you have the metadata importer, you can start to enumerate all the types in the metadata file:
Pointer enum = null;
mdTypeDef typeID;
Int32 nRead;
while (reader.EnumTypeDefs(enum, out typeID, 1, out nRead) = S_OK)
{
ProcessToken(reader, typeID);
}
reader.CloseEnum(enum);
And now for each typeID in the winmd you can get various properties:
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);
}
Now's we're at the point where there's some trickery needed when getting information about a type:
.winmd
itself: use GetTypeDefPropsThe only way to tell the difference is to try to read the type properties assuming it is a type definition using GetTypeDefProps and check the return value:
S_OK
it's a type referenceS_FALSE
it's a type definition
Get the properties of the type, including:
void GetTypeInfo(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 as 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;
}
}
There's some other interesting gotchas if you're trying to decipher types. In the Windows Runtime, everything is either fundamentally:
Structs and Enums are also classes; but descendant of a specific class:
System.ValueType
--> structSystem.Enum
--> enumInvaluable assistance came from:
which i believe is the only documentation is existence on reading metadata from an EMCA-335 assembly using Microsoft's API.