COM registration for cross process access

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
Key: HKEY_CLASSES_ROOT\Interface\<IID>\InProcServer32\
    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:

Key: HKEY_CLASSES_ROOT\Interface\<IID>\
    Default Value: <friendly name for the interface> Again, not really required, but nice for oleview
Key: HKEY_CLASSES_ROOT\Interface\<IID>\ProxyStubClsid32\
    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?

Comments

  • Anonymous
    January 06, 2006
    The comment has been removed
  • Anonymous
    January 06, 2006
    Isn't there a way to do this without using a custom proxy dll? (doesn't result in minimal data in the registry, but I think it results in a fair tradeoff in required effort)
  • Anonymous
    January 06, 2006
    The comment has been removed
  • Anonymous
    January 06, 2006
    Each time I read a COM article by you, Raymond or whoever it increases two things: my realisation that I don't really know much about COM, and my fear that learning more about it will drive me insane.

    I'm ashamed to say that I've never really played much with COM at its lowest level. I've dealt with COM objects from scripting languages, obviously. However, each time I learn about the internals it seems to get more and more complicated to the point where I start forgetting what I learned to start with.

    I'm glad the .NET framework unites all of these different COM thingies (Code, Interfaces, Type Libraries, Proxy DLLs, ...) into a small set of simple concepts that I've actually had time to figure out. (I think!)

    However, part of me still wants to take the time to figure it out just for the sake of understanding it. I'll probably learn about it when it's obsolete just like I've done with most technologies that seemed clever at the time, since they seem so much more exciting and quaint with the benefit of hindsight! :)
  • Anonymous
    January 06, 2006
    Dave: the .NET framework installs an OLE MIME filter for PE executables (application/x-msdownload, and application/octet-stream too, just in case), so that if an executable pulled in through an <object> is found to be a .NET application or assembly, it's instantiated in the Internet security zone (thus sandboxed so that it cannot do any damage, and as safe as a Java applet would be). You really don't need anything else to embed .NET code in a webpage. Here's the detailed documentation:

    http://msdn.microsoft.com/library/en-us/cpguide/html/cpcondeployingcommonlanguageruntimeapplicationusingie55.asp
  • Anonymous
    January 06, 2006
    Stephane, COM provides a convenient activation model, and there are times you simply must use COM (for example, if you're a shell plugin, you're pretty much stuck with COM).

    And as far as using sockets when running out-of-proc, COM/RPC has some HUGE advantages - for example, COM and RPC can use the LPC transport which is significantly faster than sockets. And you don't have to deal with manually marshalling all your data structures. Also COM and RPC provide contract validation for free - DCOM/RPC guarantee that the contract specified in the IDL file is enforced - if you specify that a pointer parameter is non optional, you'll never get a NULL pointer. If you say that a buffer is <n> bytes large, you can guarantee that you've got at least <n> bytes. With sockets you have to do all that work yourself.

    mirobin, you don't need the proxy DLL, you can use a typelib, but (as I mentioned), marshalling isn't full fidelity. How to add typelib info is next.

  • Anonymous
    January 06, 2006
    The comment has been removed
  • Anonymous
    January 06, 2006
    The comment has been removed
  • Anonymous
    January 07, 2006
    Christian, why not the posts?

    As I mentioned in the other post, there are LOTS of COM objects out there with way too much unnecessary information in their COM definitions. And reducing information in the registry is a good thing.

    Think of it this way. There are several thousand COM objects registered in Windows by default. If half of them have two or three unnecessary keys, each key takes up say a hundred bytes, that means that there are maybe twenty or thirty k of information in the registry. The entire registry is paged into memory during boot, which means that removing that unnecessary data will reduce the size of the registry by maybe 5 pages or so. That means that there are 40 fewer sectors read from the disk during boot. To read each sector takes somewhere around 10 milliseconds (it doesn't take that much if they're contiguous, but bear with me). That means that by removing the unnecessary information from the registry you could speed up Windows boot by 400 milliseconds or so.

    Of course these numbers are all SWAGs, but you get the point - if there's less stuff in the registry, the system boots faster.

    So it's worth documenting this stuff.
  • Anonymous
    January 08, 2006
    The comment has been removed
  • Anonymous
    January 10, 2006
    I have to echo Ben Cooke's comment.

    An example is your note about the ENTRY_PREFIX macro. It does not seem to be officially documented anywhere except in the rpcproxy.h header itself.

    It really does need some bit of documentation, since if you want to use it to make a single DLL that can register both the in-proc COM server and the proxy server you must know that the DllRegisterServer and DllUnregisterServer routines that register the in-proc service must call through to the proxy versions as well.

    It looks like nothing has to be done for the Class Factory export - it appears that the system calls NdrDllGetClassObject instead of DllGetClassObject to get a class factory interface pointer (actually an IPSFactoryBuffer pointer for proxies). I have not verified this with a debugger, but that's what it looks like form examining the headers.

    Since obviously there are plenty of people using COM, I guess most COM programmers just let the 'magic' happen and don't really care how this stuff works.

    That said, I like these posts on COM you're making because they shed a lot of light on what goes on fundamentally with COM, and it seems I'm going to be using a lot of this information in the coming months.
  • Anonymous
    January 10, 2006
    Mikeb: the ENTRY_PREFIX macro is actually documented on the linked page (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/midl/midl/c_compiler_definitions_for_proxy_stubs.asp).

    I was surprised when I started this to find the documentation - it was better than I'd thought.
  • Anonymous
    February 01, 2006
    Yesterday, someone asked me how to inform client about outof process object it is using is crashed