winapicomwindows-runtimemidlwinmd

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


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


Solution

  • 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:

    You might try the full name of the attribute class:

    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":

    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