How to get the Interface ID (IID, i.e. the GUID) of an interface when importing a WinRT winmd?

2.7k Views Asked by At

Short version

How to you get the interface identifier (IID) for an interface from a *.winmd file when using IMetadataImport?

e.g. Windows.Globalization.ICalendar: {CA30221D-86D9-40FB-A26B-D44EB7CF08EA}

Longer Version

A good example is Windows.Globalization.ICalendar interface. Its IID is CA30221D-86D9-40FB-A26B-D44EB7CF08EA.

It's in the IDL

You can find it in the source Windows.Globalization.idl file:

[exclusiveto(Windows.Globalization.Calendar)]
[uuid(CA30221D-86D9-40FB-A26B-D44EB7CF08EA)]
[version(0x06020000)]
interface ICalendar : IInspectable
{
   //...snip...
}

Reminder: You're not supposed to parse these files. It gets compiled into a *.winmd assembly, and that database is the ground-truth.

It's in the header

You can find it in the windows.globalization.h file, which was generated from the *.winmd using an import tool:

namespace ABI {
    namespace Windows {
        namespace Globalization {
            
            MIDL_INTERFACE("CA30221D-86D9-40FB-A26B-D44EB7CF08EA")
            ICalendar : public IInspectable
            {
               //...snip...
            }

It's even in the winmd

You can even find the InterfaceID in the resulting compiled *.winmd assembly database:

enter image description here

But how do I get it when using the documented IMetadataImporter API?

Code

The abridged version of how to get up and running reading winmd metadata files:

// Create your metadata dispenser:
IMetadataDispsener dispener;
MetaDataGetDispenser(CLSID_CorMetaDataDispenser, IMetaDataDispenser, out dispenser);

//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.

Bonus Reading

  • MSDN Blogs: Metadata Unmanaged API (a preliminary PDF version of an old Word document that, as far as i can tell, is the only Microsoft documentation for the Metadata API) (archive)
1

There are 1 best solutions below

0
On BEST ANSWER

Short Version

The custom attribute blob is the C# serialized format of a Guid class:

3.2.2 DefineCustomAttribute

The format of pBlob for defining a custom attribute is defined in later in this spec. (broadly speaking, the blob records the argument values to the class constructor, together with zero or more values for named fields/properites – in other words, the information needed to instantiate the object specified at the time the metadata was emitted). If the constructor requires no arguments, then there is no need to provide a blob argument.

4.3.6 GetCustomAttributeProps

A custom attribute is stored as a blob whose format is understood by the metadata engine, and by Reflection; essentially a list of argument values to a constructor method which will create an instance of the custom attribute.

In order to get the GuidAttriute guid value, you have to emulate C#'s deserializing a Guid object from a stream.

Long Version

Starting with your IMetadataImport you call IMetaDataImport.GetCustomAttributeByName.

The first tricky part is figuring out the name of the attribute I'm after. I know it's Guid when viewed in IDL or C#:

[Guid("CA30221D-86D9-40FB-A26B-D44EB7CF08EA")]
interface ICalendar
{
    //...
}

And that underneath it would actually be called "GuidAttribute". But neither of those actually work:

  • "Guid": Fails with S_FALSE
  • "GuidAttribute": Fails with S_FALSE

You might try the full name of the attribute class:

  • "System.Runtime.InteropServices.GuidAttribute"

But that also fails because that's the name of the GuidAttribute class in the .NET framework. In the WinRT library you have to use "Windows.Foundation.Metadata.GuidAttribute":

  • "Guid": Fails with S_FALSE
  • "GuidAttribute": Fails with S_FALSE
  • "System.Runtime.InteropServices.GuidAttribute": Fails with S_FALSE (CLR only)
  • "Windows.Foundation.Metadata.GuidAttribute": Works

Now that we figured out the name of the attribute to find, we can query for it:

mdToken calendarTokenID = 0x02000022; //Windows.Globalization.ICalendar
String  attributeName   = "Windows.Foundation.Metadata.GuidAttribute";

Pointer blob;
UInt32 blobLen;
reader.GetCustomAttributeByName(calendarTokenID, attributeName, out blob, out blobLen);

The next tricky part is is decoding the blob.

Decoding the blob

Custom attributes each have a different serialization formats. The blob is essentially passed to the Attribute's constructor. The serialization format is the same as the C# serialization format.

For GuidAttribute attributes, the binary serialization format is 20-bytes:

01 00                                            Prolog (2-bytes)       0x0001 ==> version 1
1D 22 30 CA D9 86 FB 40 A2 6B D4 4E B7 CF 08 EA  Guid (16-bytes)        "CA30221D-86D9-40FB-A26B-D44EB7CF08EA"
00 00                                            Trailing null (2-bytes)

The easiest way for me to extract the Guid is to declare a matching structure, cast the returned pointer to a type of that structure, and access the Guid member:

struct SerializedGuidAttribute
{
   UInt16 prolog; //2-bytes. 0x0001 
   Guid   guid;   //16-byte guid
   UInt16 footer; //2-byte footer
}
typedef SerializedGuidAttribute* PSerializedGuidAttribute;

Guid guidAttriute = PSerializedGuidAttribute(blob).guid;

And you have it

Guid GetGuidAttribute(IMetadataReader reader, mdToken intf)
{
   Pointer blob;
   UInt32 blobLen;
   reader.GetCustomAttributeByName(intf, "Windows.Foundation.Metadata.GuidAttribute", 
         out blob, out blobLen);

   //if (blobLen != 20) { throw new Exception("Something") };

   return PSerializedGuidAttribute(blob).guid;
}

Bonus

  • Microsoft Metadata API documentation