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?
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:
CLSID_CorMetaDataDispenser
implementation that ships with the SDKThe 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.