Update 2021-04-20: The code presented here is for illustration purposes only. As pointed out by Simon Mourier, for marshaling in-process of such a simple class there is no need for all the TLB shenanigans. In reality, the TLB is provided by a third-party, with the interface in question serving for callbacks. The object calling the interface resides in another process, however, so I really do have to marshal the interface after implementing it. As demonstrating the whole inter-process flow is tedious, I opted for something simpler - in-process inter-apartment marshaling.
Suppose I have the following type library:
import "oaidl.idl";
import "ocidl.idl";
[
uuid(99CF9EB9-9B6E-4D44-B73C-6BB8FCD45B82),
version(1.0),
]
library IsThisRealMarshal
{
[
uuid(80997EA1-0144-41EC-ABCF-5FAD08D5A498),
nonextensible,
]
dispinterface IMyInterface
{
properties:
methods:
[id(1)]
void Method();
};
};
I would like to marshal IMyInterface
to another apartment. Since it's a dispinterface, I would like to use the OLE marshaler for this. And so, I register the type library:
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\SOFTWARE\Classes\TypeLib\{99CF9EB9-9B6E-4D44-B73C-6BB8FCD45B82}]
[HKEY_CURRENT_USER\SOFTWARE\Classes\TypeLib\{99CF9EB9-9B6E-4D44-B73C-6BB8FCD45B82}\1.0]
[HKEY_CURRENT_USER\SOFTWARE\Classes\TypeLib\{99CF9EB9-9B6E-4D44-B73C-6BB8FCD45B82}\1.0\0]
[HKEY_CURRENT_USER\SOFTWARE\Classes\TypeLib\{99CF9EB9-9B6E-4D44-B73C-6BB8FCD45B82}\1.0\0\win32]
@="path\\to\\library.tlb"
And the interface (setting the proxy CLSID to that of the OLE marshaler):
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\SOFTWARE\Classes\Interface\{80997EA1-0144-41EC-ABCF-5FAD08D5A498}]
[HKEY_CURRENT_USER\SOFTWARE\Classes\Interface\{80997EA1-0144-41EC-ABCF-5FAD08D5A498}\ProxyStubClsid32]
@="{00020424-0000-0000-C000-000000000046}"
[HKEY_CURRENT_USER\SOFTWARE\Classes\Interface\{80997EA1-0144-41EC-ABCF-5FAD08D5A498}\TypeLib]
@="{99CF9EB9-9B6E-4D44-B73C-6BB8FCD45B82}"
"Version"="1.0"
And I try to marshal (error-checking omitted for brevity):
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
CComPtr<IMyInterface> object {};
object.Attach(new MyObject);
CComPtr<IGlobalInterfaceTable> git {};
git.CoCreateInstance(CLSID_StdGlobalInterfaceTable, nullptr, CLSCTX_INPROC_SERVER);
DWORD cookie = 0;
git->RegisterInterfaceInGlobal(object, __uuidof(IMyInterface), &cookie);
auto thread = std::thread([cookie]
{
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
CComPtr<IGlobalInterfaceTable> git {};
git.CoCreateInstance(CLSID_StdGlobalInterfaceTable, nullptr, CLSCTX_INPROC_SERVER);
CComPtr<IMyInterface> object {};
git->GetInterfaceFromGlobal(cookie, __uuidof(IMyInterface), (void **)&object);
});
thread.join();
Where the MyObject
class implements the bare minimum COM functionality:
class MyObject : public IMyInterface
{
private:
std::atomic<ULONG> _refcount = 1;
public:
MyObject() = default;
MyObject(MyObject const &) = delete;
MyObject & operator=(MyObject const &) = delete;
HRESULT QueryInterface(const IID& riid, void** ppvObject) override
{
if (nullptr == ppvObject)
{
return E_POINTER;
}
if (riid == __uuidof(IUnknown))
{
*ppvObject = static_cast<IUnknown *>(this);
}
else if (riid == __uuidof(IDispatch))
{
*ppvObject = static_cast<IDispatch *>(this);
}
else if (riid == __uuidof(IMyInterface))
{
*ppvObject = static_cast<IMyInterface *>(this);
}
else
{
*ppvObject = nullptr;
return E_NOINTERFACE;
}
static_cast<IUnknown *>(*ppvObject)->AddRef();
return S_OK;
}
ULONG AddRef() override
{
return ++_refcount;
}
ULONG Release() override
{
auto const new_refcount = --_refcount;
if (0 == new_refcount)
{
delete this;
}
return new_refcount;
}
HRESULT GetTypeInfoCount(UINT* pctinfo) override
{
return E_NOTIMPL;
}
HRESULT GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo) override
{
return E_NOTIMPL;
}
HRESULT GetIDsOfNames(const IID& riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId) override
{
return E_NOTIMPL;
}
HRESULT Invoke(DISPID dispIdMember, const IID& riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams,
VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr) override
{
return E_NOTIMPL;
}
};
Unfortunately, the call to GetInterfaceFromGlobal
fails with E_FAIL
.
Debugging reveals that none of the IDispatch
methods are called, only the IUnknown
ones. Additionally, it appears that the E_FAIL
originates from combase!CheckTypeInfo
. First, this function uses ITypeInfo::GetTypeAttr
to retrieve information about IMyInterface
:
It then proceeds to check whether the flags TYPEFLAG_FDUAL
(0x40
) or TYPEFLAG_FOLEAUTOMATION
(0x100
) are present in the wTypeFlags
field of the TYPEATTR
structure:
Since neither of these flags are present (the field has the value 0x1080
, and indeed the IDL doesn't mark the interface as either [oleautomation]
or [dual]
), the function fails with E_FAIL
.
What am I doing wrong? And if the OLE marshaler indeed cannot marshal this interface, is there anything I can do apart from implementing IMarshal
myself, assuming I cannot modify the IDL?
With the help of Simon Mourier's code, I managed to find the problem. The problem was that I used the PSOAInterface
proxy ({00020424-0000-0000-C000-000000000046}
). Since IMyInterface
is not an OLE Automation interface (i.e. not marked with [oleautomation]
), this rightly failed.
The solution is to use the PSDispatch
proxy ({00020420-0000-0000-C000-000000000046}
), which is capable of marshaling pure IDispatch
interfaces.