I'm trying to marshal an interface to another thread.
Windows provides the convenient CoMarshalInterThreadInterfaceInStream
helper function to take care of the boilerplate code associated with using CoMarshalInterface
directly.
const Guid CLSID_Widget = "{F8383852-FCD3-11d1-A6B9-006097DF5BD4}";
const Guid IID_IWidget = "{EBBC7C04-315E-11D2-B62F-006097DF5BD4}";
//Create our widget
HRESULT hr = CoCreateInstance(CLSID_Widget, null,
CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,
IID_IWidget, out widget);
OleCheck(hr);
//Marshall the interface into an IStream
IStream stm;
hr = CoMarshalInterThreadInterfaceInStream(IID_IWidget, widget, out stm);
OleCheck(hr);
Except that the call to CoMarshalThreadInterfaceInStream
fails with:
REGDB_E_IIDNOTREG (0x80040155)
Interface not registered
Go directly to CoMarshalInterface
The COM API function CoMarshalInterThreadInterfaceInStream provides a simple wrapper around CreateStreamOnHGlobal
and CoMarshalInterface
, as shown here:
// from OLE32.DLL (approx.)
HRESULT CoMarsha1InterThreadInterfaceInStream(
REFIID riid, IUnknown *pItf, IStream **ppStm)
{
HRESULT hr = CreateStreamOnHGlobal(0, TRUE, ppStm);
if (SUCCEEDED(hr))
hr = CoMarshalInterface(*ppStm, riid, pItf,
MSHCTX_INPROC, 0, MSHLFLAGS_NORMAL);
return hr;
}
So we can try ourselves.
IStream stm = new Stream()
hr = CoMarshallInterface(stm, IID_IWidget, widget,
MSHCTX_INPROC, // destination context is in-process/same host
NULL, // reserved, must be null
MSHLFLAGS_NORMAL // marshal once, unmarshal once
);
OleCheck(hr);
But that fails with:
REGDB_E_IIDNOTREG (0x80040155)
Interface not registered
Use standard marshaling
My class does not implement IMarhsal
interface. This is right and normal.
By default, when
CoMarshalInterface
is first called on an object, the object is asked whether it wishes to handle its own cross-apartment communications. This question comes in the form of aQueryInterface
request for theIMarshal
interface. Most objects do not implement theIMarshal
interface and fail thisQueryInterface
request, indicating that they are perfectly happy to let COM handle all communications via ORPC calls. Objects that do implement theIMarshal
interface are indicating that ORPC is inappropriate and that the object implementor would prefer to handle all cross-apartment communications via a custom proxy. When an object implements theIMarshal
interface all references to the object will be custom marshaled.When an object does not implement the
IMarshal
interface, all references to the object will be standard marshaled. Most objects elect to use standard marshaling.
So the the question becomes why is the standard COM marshaler having so much problems? What is the source of the error Interface not registered?
The interface is, in fact, not registered
The requires for COM are undocumented, but i can tell you that my interface GUID does not exist in:
HKEY_CLASSES_ROOT\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}
The reason for this will be explained at the end.
I know that Windows provides you the CoRegisterPSClsid
function that allows you to register an interface inside your process, so that the standard marshaler will be able to marshal it:
Enables a downloaded DLL to register its custom interfaces within its running process so that the marshaling code will be able to marshal those interfaces.
HRESULT CoRegisterPSClsid( _In_ REFIID riid, _In_ REFCLSID rclsid );
Parameters:
riid
[in]: A pointer to the IID of the interface to be registered.rclsid
[in]: A pointer to the CLSID of the DLL that contains the proxy/stub code for the custom interface specified by riid.
Which i can try calling, but what clsid do i use?
CoRegisterPSClsid(IID_IWidget, ???);
What is the CLSID of the DLL that contains the proxy/stub code for the custom interface specified by riid? Do i use my class itself?
CoRegisterPSClsid(IID_IWidget, CLSID_Widget);
That doesn't sound right; but i don't understand the COM standard marshaler well enough. Doesn't that CLSID have to be one of the standard COM marsharling classes; implementing IPSFactoryBuffer
?
Either way, it doesn't work. I still get the error "Interface not registered".
Register the interface
I of course can register my interface in the registry:
HKEY_CURRENT_USER\Software\Classes\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}
(default) = "IWidget"
But that doesn't fix it. Spelunking through the Interface
registry keys, i notice that many specify a ProxyStubClsid32 entry.
When a new interface is requested on an object, the proxy and stub managers must resolve the requested IID onto the CLSID of the interface marshaler. Under Windows NT 5.0, the class store maintains these mappings in the NT directory, and they are cached at each host machine in the local registry. The machine-wide IID-to-CLSID mappings are cached at
HKEY_CLASSES_ROOT\Interface
and the per-user mappings are cached at
HKEY_CURRENT_USER\Software\Classes\Interface
One or both of these keys will contain a subkey for each known interface. If the interface has an interface marshaler installed, there will be an additional subkey (ProxyStubClsid32) that indicates the CLSID of the interface marshaler.
Except what class implements marshaling? I don't have a marshaler.
Can COM automatically marshal based on a TypeLibrary
Is it possible that if i register a Type Library with my Interface, that COM's standard marshaler will be able to bootstrap a proxy class on the fly?
I registered my interface above. Now i manually include the TypeLibrary:
HKEY_CURRENT_USER\Software\Classes\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}\TypeLib
(default) = "{38D528BD-4948-4F28-8E5E-141A51090580}"
And if i monitor the registry during the call to CoMarshalInterface
i see that it attempts, and does find, my Interface IID:
- Operation:
RegOpenKey
- Path:
HKCR\WOW6432Node\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}
- Result:
SUCCESS
It then tries to look for a ProxyStubClsid32, and fails:
- Operation:
RegOpenKey
- Path:
HKCR\WOW6432Node\Interface\{668790E3-83CC-47E0-907F-A44BA9A99C8D}\ProxyStubClsid32
- Result:
NAME NOT FOUND
My hope would then be that the standard COM marshaler attempts to look for:
- Operation:
RegOpenKey
- Path:
HKCR\WOW6432Node\Interface\{668790E3-83CC-47E0-907F-A44BA9A99C8D}\TypeLib
But it doesn't.
The OLE Automation marshaler
According to Don Box, the Ole Automation marshaler (PSOAInterface - {00020424-0000-0000-C000-000000000046}) is able to build a stub/proxy out of a type library:
The Type Library Marshaler
When these specially annotated interfaces are encountered by RegisterTypeLib (or LoadTypeLib in legacy mode), COM adds
ProxyStubClsid32
entries for the interface with the value{00020424-0000-0000-C0000-000000000046}
. This GUID corresponds to the class PSOAInterface that is registered as living in OLEAUT32.DLL, the OLE automation DLL. Because of the DLL that it lives in, this marshaler is sometimes called the[oleautomation]
marshaler, although it is also called the type library marshaler or the universal marshaler. I'll refer to it as the type library marshaler, since it really has very little to do with IDispatch. (In fact, it is common to use the[oleautomation]
attribute on interfaces that don't derive from IDispatch directly or indirectly.)The class factory for the type library marshaler does something very tricky in its CreateProxy and CreateStub routines. Rather than return a statically compiled vtable (which is impossible given the fact that the requested interface didn't exist when OLEAUT32.DLL was built as part of the OS), the type library marshaler actually builds an /Oicf-style proxy and stub based on the type library for the interface. Because there is no efficient way to find the
ITypeInfo
for an arbitrary interface, the LIBID and version of the interface's type library must be stored under the:HKCR\Interface\{XXX}\TypeLib
registry key.
I tried to set PSOAInterface
as my interface's ProxyStub class:
Register standard COM class PSOAInterface as our proxy stub clsid
HKEY_CURRENT_USER\Software\Classes\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}\ProxyStubClsid32 (default) = "{00020424-0000-0000-C0000-000000000046}"
Register our type library for our interface
HKEY_CURRENT_USER\Software\Classes\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}\TypeLib (default) = "{38D528BD-4948-4F28-8E5E-141A51090580}" Version = "1.0"
The type library itself is already registered:
HKEY_CURRENT_USER\Software\TypeLib\{38D528BD-4948-4F28-8E5E-141A51090580}\1.0\0\win32 (default) = "D:\Junk\ComLibraryProxyTest\Win32\Project1.dll"
But it still fails.
- it reads
HKCR\Interface\[IID_IWidget]
- it reads
HKCR\Interface\[IID_IWidget]\ProxyStubClsid32
But it:
- never reads:
HKCR\Interface\[IID_IWidget]\TypeLib
So it fails to proxy the object.
Questions
- Is it possible for the standard COM "Ole Automation" marshaler to build a proxy class out of a Type Library at runtime?
- Is it possible for me to build a proxy class out of a Type Library at runtime?
Background
CoMarshalInterface
takes an interface pointer on input and writes the
serialized representation of the pointer to a caller-provided byte stream. This byte stream can then be passed to another apartment, where the
CoUnmarshalInterface
API function uses the byte stream to return an interface
pointer that is semantically equivalent to the original object yet can be
legally accessed in the apartment that executes the CoUnmarshalInterface
call. When calling CoMarshalInterface
, the caller must indicate how far away
the importing apartment is expected to be. COM defines an enumeration for
expressing this distance:
enum MSHCTX {
MSHCTX_INPROC = 4, // in-process/same host
MSHCTX_LOCAL = 0, // out-of-process/same host
MSHCTX_NOSHAREDMEM = 1, //16/32 bit/same host
MSHCTX_DIFFERENTMACHINE = 2, // off-host
};
It is legal to specify a greater distance than required, but it is more efficient
to use the correct MSHCTX when possible. CoMarshalInterface
also allows
the caller to specify the marshaling semantics using the following marshal flags:
enum MSHLFLAGS {
MSHLFLAGS_NORMAL, // marshal once, unmarshal once
MSHLFLAGS_TABLESTRONG, // marshal once, unmarshal many
MSHLFLAGS_TABLEWEAK, // marshal once, unmarshal many
MSHLFlAGS_NOPING = 4 // suppress dist. garbage collection
Normal marshaling (sometimes called call marshaling) indicates that the
marshaled object reference must be unmarshaled only once, and if additional
proxies are needed, additional calls to CoMarshalInterface
are required. Table
marshaling indicates that the marshaled object reference may be unmarshaled
zero or more times without requiring additional calls to CoMarshalInterface.
I think that all COM object type libraries, when compiled by MIDL, can automatically create a proxy/stub factory. But in my case:
if the COM standard marshaler can't find a proxy/stub factory for an interface, it returns the error REGDB_E_IIDNOTREG.
It may be the case that i have to either:
- use CreateProxyFromTypeInfo and CreateStubFromTypeInfo to create my own proxy
- let the standard COM marshaler automatically create a proxy/stub if there's a typeinfo chunk associated with the interface GUID.
Bonus Reading
- Old New Thing: What are the rules for CoMarshalInterThreadInterfaceInStream and CoGetInterfaceAndReleaseStream?
- Old New Thing: Why do I get the error REGDB_E_IIDNOTREG when I call a method that returns an interface?
- Old New Thing: Why do I get E_NOINTERFACE when creating an object that supports that interface?
- Don Box - MSJ: Standard Marshalling in COM (archive)
- Mysterious disappearing acts by samples from Microsoft SDKs