As anyone who consumes COM objects or develops them is acutely aware, they require registration. Indeed, the original role of the “Registry”, in 16-bit Windows prior to the release of Windows 95, was to contain this COM registration information.
Typically, the in-process server DLL or the out-of-process EXE associated with the implementation of an object with a given CLSID are declared in a subkey with the CLSID as its name under HKEY_CLASSES_ROOT\CLSID (as a side note, HKEY_CLASSES_ROOT is a unified view of HKEY_LOCAL_MACHINE\SOFTWARE\Classes and and HKEY_CURRENT_USER\SOFTWARE\Classes, allowing for per-user class registration).
Furthermore, if your objects use any custom interfaces (they probably do), a proxy/stub factory is required for marshalling them across apartments in the same process or across processes in the same machine or in different machines in the network. The way this usually works is with a subkey named after the IID in question under HKEY_CLASSES_ROOT\Interface. In there a ProxyStubClsid32 value specifies the DLL that implements a proxy/stub factory for the interface in question. Rather than coding that yourself, you often let MIDL, the IDL compiler, generate this for you from the .idl file describing your custom interface and link the generated code to create this DLL.
Registering your CLSID implementation and the proxy/stub factories for your IIDs is desirable in most scenarios, but as software becomes more complex and the deployment scenarios more numerous, COM registration can become a source of pain and agony. Microsoft themselves recognized the migration of “DLL Hell”-style problems into the realm of COM and introduced a registration-free COM feature with the “Fusion” SxS (side by side) loader included starting with Windows XP, based on XML manifests.
Those of us who have been following the literature on COM over the years consider this development quite ironic, since COM has been “sold” as solving the world’s version issues since the era it was still being called “OLE 2.” I guess version-based ProgIDs aren’t enough.
Anyway, Fusion’s XML-based manifest solution is decent enough. You deploy a side-by-side assembly (unfortunately, Microsoft’s vendor lock-in forces you to do this with Windows Installer at the moment, an issue I’ll discuss in a future post) and describe your exported COM objects by CLSID in the XML manifest, describing the server type, ThreadingModel and so on. A facility for registering proxy/stub factories is also provided. You attach the XML manifest to your DLL either as an inline resource (a Win32 resource in text XML format – it is obvious this is the meeting point of technologies from different eras) or as an external XML file, named name.dll.manifest for a DLL called name.dll. Your client application binds to the specific version of the COM object of interest either in an XML manifest of its own (the easy way) or if you really want to spice things up, uses the not so well-known Fusion APIs, CreateActCtx and ActiveActCtx to get the client to bind to the desired implementation. Unfortunately Fusion’s API is a little “square”, requiring you to specify the manifest either as the path to a PE image (EXE/DLL) and the name of its embedded manifest resource or a .manifest file. You can’t specify a buffer containing XML describing your binding requirements or better yet specify some structure with that information. Obviously, the Fusion guys didn’t have dynamic code generation in the unmanaged code realm in mind.
You can read more about registration free COM in Junfeng Zhang’s blog. Older entries in his blog have a wealth of Fusion-related information. Two specific posts of interest are this one and this one. The rest of the stuff in the Fusion category are also a good read.
The fact the Fusion native loader is only available starting with Windows XP and the unfortunate issue of its inflexibility in specifying binding information presents a difficulty for those of us who wish to deploy isolated COM components in a heterogeneous environment. Yet this same heterogeneous environment is exactly that which presents us with the most difficult version compatibility issues, bringing us to require side by side components in the first place. A solution independent of Fusion is desired.
First we must understand the basic mechanics of COM activation. When we try to retrieve a CLSID’s class object (either directly with CoGetClassObject or indirectly with CoCreateInstance, etc.) the COM runtime (which mainly consists of ole32.dll, rpcrt4.dll and rpcss.exe) will first try to find a running instance of the class factory, before turning to the registry for activation information. It is this behavior that makes the second instantiation request for an object implemented in a .EXE local server recycle the existing class factory from the server rather than create another instance of the .EXE specified in the registry’s LocalServer32 value (note that I do not consider here the case of REGCLS_SINGLEUSE vs. REGCLS_MULTIPLEUSE, etc., but rather discuss the typical scenario). We can conclude from this behavior that registration for local servers is for activation purposes, since locating running instances is done independently of the registry (presumably with the Running Object Table).
CoRegisterClassObject is the COM runtime API running local servers use to announce the local server’s class factory is active and that activation of the server is no longer required for instantiating clients. Microsoft’s official documentation states the API is only intended for EXE servers and that DLLs should export DllGetClassObject instead. However, this appears to be an attempt to prevent confusion by novices rather than anything else. The fact is, your COM DLL can avoid a registry entry and register a class factory for in-process clients to use. Similarly, a local server could settle for calling this API and not registering itself in the registry. This of course means you give up COM’s activation facility. Trying to create objects before your class factory has “magically” come to life fails as though the class wasn’t registered. Once registration is performed, objects can be created.
At this point our strategy is hindered by the mechanics of COM’s marshaling architecture. It is not unusual for us to want our objects to be accessible beyond the confines of the apartment they were created in, and indeed, beyond their hosting process. We need to get around COM’s expectation that the proxy/stub factories for our custom interfaces be specified in the registry.
One solution is to statically link the proxy/stub factory for the custom interfaces we use into our clients and servers (this isn’t as bad as it sounds – they already have a deep intimacy with the custom interfaces seeing as they know them at the vtable and argument passing level). For an interfaces defined in name.idl, typically name_p.c is the name of MIDL’s generated proxy/stub factory. The actual implementation is in fact resident in ole32.dll, while MIDL’s output is some sort of descriptor table for that implementation to use with your specific custom interface. Usually name_p.c contains implementations of functions expected from a COM DLL, like DllGetClassObject. To prevent conflicts, we define the ENTRY_PREFIX preprocessor directive which allows us to prefix those generated functions with a name. Assuming we #define ENTRY_PREFIX PrxStb, for instance, We can call PrxStbDllGetClassObject to retrieve the class factory for the proxy/stubs for our interfaces. We now need to make the COM runtime understand this retrieved instance is the proxy/stub factory we need and that it has no business looking in the registry. CoRegisterPSClsid is just the API for the job. When a proxy/stub factory CLSID is registered using this API, the COM runtime no longer requires a ProxyStubClsid32 entry for our custom interface in the registry. Since we specify a CLSID here, we need to either write information about this CLSID to the registry or provide both the client and server side, separately, with running instances of the factory registered with CoRegisterClassObject (just like our actual object) so they’d be able to use it, creating proxies in the client side and stubs in the server side. A noteworthy (and apparently not-so-documented) fact to consider at this stage is that the CLSID for interface proxy/stub factories generated by MIDL is equal to the IID of the custom interface in question – just in case you were wondering what CLSID to create, register and map to the interface.
It is important to note that you must register an instance of the proxy/stub factory in each and every apartment you wish it to be available for object use, otherwise COM will unsuccessfully attempt to find a proxy for your proxy since it is only available elsewhere.
I’ve seen fragments of this solution discussed on the web and on various newsgroups, but its complete specification was always missing, so I hope this helps. However, there is another noteworthy solution to consider.
The second solution is only slightly different from the first one and it is unclear whether it has any advantages. It is only noteworthy because it both utilizes undocumented APIs, which seem to be barely mentioned in a web search and is implemented in a sample from Microsoft’s .NET 1.1 Framework SDK. Indeed, it appears this underground sample was scrapped from later .NET SDKs and is missing from the current Windows SDK. The “Marshaler” sample uses CreateProxyFromTypeInfo and CreateStubFromTypeInfo, exported from rpcrt4.dll, to create proxies and stubs for arbitrary interfaces specified by a given ITypeInfo* (which you can easily give it for your custom interface if you create a Type Library for it). If you have another reason to deploy a type library as a part of your isolated component, you may consider this nicer than statically linking to the MIDL-generated code.
Both of the these non-Fusion approaches have the apparent advantage of universality (although I haven’t checked whether they work on the deprecated Windows 9x family). However, keep in mind they are only useful to you if you are willing to give up on one of COM’s most notable features – activation. Without the registry, you alone are left in charge of bringing your class factories to life, making sure you create them as needed and where needed, considering threading model and other issues. Nevertheless, even in XP and later systems, these isolation approaches may be considered to have advantages over their Fusion counterpart.
If only everybody just had the CLR…