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

85 Views Asked by At

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?

1

There are 1 best solutions below

2
IInspectable On BEST ANSWER

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:

  • Work through ECMA-335 (Partition II) and implement it
  • Go undocumented and use the CLSID_CorMetaDataDispenser implementation that ships with the SDK

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.