Yesterday I posted a minimal COM registration. But it had some serious issues. Among them the COM objects couldn’t be used cross process, and they couldn’t be used from a STA application unless the object aggregated the free threaded marshaller.
So what if you want to go cross-process? Well, in order to go cross process, you need to be able to know how to marshal the parameters for your interfaces. COM knows how to marshal the standard interfaces (like IClassFactory, IUnknown, etc) but most objects need more than just those interfaces.
There are basically two ways of letting COM know about your interfaces. The first is by using a typelib, the second is by using a proxy DLL. If you don’t need to worry about COM interop or interaction with older scripting architectures (like VB6 or script), then using the proxy DLL is unquestionably the way to go – when structures are mapped to a typelib, there is a slight loss of fidelity which can cause “issues”. As an example of the loss of fidelity, typelibs can’t contain unnamed unions, while C allows them. Thus if an interface attempts to marshal a structure containing an unnamed union, the typelib will replace the unnamed union with a named union. Normally this isn’t a problem, the actual structure data doesn’t change, but it means that the definitions don’t round-trip.
I’m not going to discuss typelibs currently (they’re the next post in this mini-series), this time I want to talk about using proxy DLLs for your interface marshaling.
A proxy DLL is simply a DLL that contains the logic needed to marshal your interfaces. To build one, follow the examples in MSDN here. There are lots of options when building proxy DLLs, personally I prefer to merge the proxy DLL with an existing DLL (it just seems cleaner), that’s a bit trickier, but not too hard (if you define the REGISTER_PROXY_DLL definition, then the _p file generated uses a hard coded name of DllRegisterServer, you need to define the ENTRY_PREFIX macro to rename the built-in name, etc). The macros to make that stuff work are described here.
Once you’ve gotten the proxy definitions for your interfaces built, you need to let COM know about them. When COM realizes it has to marshaling a COM object, it starts looking for information to let it know how to marshal its interfaces. First it checks to see if the object supports IMarshal, to let the object do custom marshaling. If that doesn’t work, it starts looking elsewhere. One of first places looks is to see if it’s been explicitly told about how to marshal the interface by looking in HKCR\Interface for the IID.
First, you need to come up with a GUID for the proxy DLL, uuidgen can come up with one quickly. And you need to let COM know about it (using the minimal set of registrations mentioned in the other article):
Key: HKEY_CLASSES_ROOT\CLSID\<PS Factory GUID>\
Default Value: <MyInterfaceName>_PSFactory // Again, not needed, but convenient
Default Value: <Proxy Server DLL>
Next, you want to register the interfaces and let COM know how to find your proxy DLL. Add the following to the registry for each of your interfaces:
Default Value: <friendly name for the interface> Again, not really required, but nice for oleview
Default Value: <Proxy Stub CLSID>
And with that, you’re done. COM can now marshal your custom interfaces across process boundaries (or apartment boundaries). Once again, 2 keys for each interface, plus 2 keys for the proxy DLL, which is a fair amount less than some of the stuff I’ve seen in the registry.
Next, what if you want to interoperate with VB or .Net?