c++cwinapiwindows-runtimewinmd

IMetaDataDispenser::DefineScope() fails with E_NOTIMPL (0x80004001)


I'm trying to write .winmd metadata that represents a (COM) API. As I understand this functionality is provided via the IMetaDataDispenser and related interfaces, that can be requested through the MetaDataGetDispenser() API.

As a first test, I was trying to instantiate an IMetaDataDispenser interface and define an empty scope (IMetaDataDispenser::DefineScope()) on it. After a bit of research, I managed to solve the former, but the latter is failing. Calling DefineScope() returns an HRESULT value of E_NOTIMPL:

#include <objbase.h>
#pragma comment(lib, "Ole32.lib")
#include <rometadata.h>
#pragma comment(lib, "Rometadata.lib")
#include <cor.h>

int main()
{
    auto hr = ::CoInitialize(nullptr);

    IMetaDataDispenser* pDispenser = NULL;
    if (SUCCEEDED(hr))
    {
        hr = ::MetaDataGetDispenser(CLSID_CorMetaDataDispenser,
                                    IID_IMetaDataDispenser, (void**)&pDispenser);
    }

    IMetaDataEmit* pEmit = NULL;
    if (SUCCEEDED(hr))
    {
        hr = pDispenser->DefineScope(CLSID_CorMetaDataRuntime, 0,
                                     IID_IMetaDataEmit, (IUnknown**)&pEmit);
    }

    if (pEmit)
        pEmit->Release();
    if (pDispenser)
        pDispenser->Release();

    return hr;
}

What is the issue here and how do I resolve it?


Solution

  • What is the issue [...]?

    In a nutshell: Part of the IMetaDataDispenser interface is intended for internal use only, with the documentation failing to make that apparent.

    It appears that the consumer-facing API surface is public. It exposes services to query the database if you already have a .winmd file. The part of the API concerned with producing a .winmd file, on the other hand, isn't accessible through the CLSID_CorMetaDataDispenser implementation that ships with the system. The only "documentation" is the respective interfaces returning E_NOTIMPL for those private parts.

    [H]ow do I resolve it?

    There isn't, to my knowledge, a public API that can be used to produce a .winmd file. The only officially supported avenue to creating a .winmd file is the midlrt.exe compiler. This works when you have a WinRT IDL (aka MIDL v3) source. Lacking that the only other options are:

    The second option was discovered by Simon in a comment. The SDK ships with a binary (midlrtmd.dll) that exports, among others, the function MetaDataGetDispenser(). Making sure that its signature matches that of the public MetaDataGetDispenser() API I went ahead and took it for a spin.

    The following implementation compiles as x64 as long as the referenced SDK is available on the system:

    #include <objbase.h>
    #pragma comment(lib, "Ole32.lib")
    #include <rometadata.h>
    #pragma comment(lib, "Rometadata.lib")
    #include <cor.h>
    
    #include <Windows.h>
    
    typedef HRESULT(STDAPICALLTYPE* fn_ptr)(REFCLSID, REFIID, LPVOID*);
    
    int main()
    {
        auto hr = ::CoInitialize(nullptr);
    
        auto const module = ::LoadLibraryW(LR"(C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\midlrtmd.dll)");
        if (!module)
            hr = E_FAIL;
    
        fn_ptr MyMetaDataGetDispenser = nullptr;
        if (SUCCEEDED(hr))
        {
            MyMetaDataGetDispenser = reinterpret_cast<fn_ptr>(::GetProcAddress(module, "MetaDataGetDispenser"));
            if (!MyMetaDataGetDispenser)
                hr = E_FAIL;
        }
    
        IMetaDataDispenser* pDispenser = NULL;
        if (SUCCEEDED(hr))
        {
            hr = MyMetaDataGetDispenser(CLSID_CorMetaDataDispenser,
                                        IID_IMetaDataDispenser, (void**)&pDispenser);
        }
    
        IMetaDataEmit* pEmit = NULL;
        if (SUCCEEDED(hr))
        {
            hr = pDispenser->DefineScope(CLSID_CorMetaDataRuntime, 0,
                                         IID_IMetaDataEmit, (IUnknown**)&pEmit);
        }
    
        if (pEmit)
            pEmit->Release();
        if (pDispenser)
            pDispenser->Release();
    
        return hr;
    }
    

    This code successfully returns an IMetaDataEmit interface that appears to be fully functional. Save()-ing it to a file shows a BSJB blob, that subsequently needs to be embedded into a PE image.

    It's not a complete .winmd file yet, but a step forward.