sean's happy programming place

The Journals of Captain COM:
Registration

Sean Baxter

The Journals of Captain COM is a sean's happy programming place exclusive column.  These articles focus on implementation of COM servers and clients in raw C++.   Expect lots of good tips and tricks for getting the most out of DevStudio and the Visual C++ extensions.  For those looking for a reference on the general workings of COM, read Essential COM by Don Box.

And without any further ado...

COM Registration

Download the source and executables for this article.

COM classes are identified by CLSID, which are indices into the registry.  Keys integral to COM are stored at these indices.  Install programs (like InstallShield) ask the COM server to register itself prior to being used.  In the case of components downloaded off the web, CoGetClassObjectFromURL will download the server, then register it before invoking CoGetClassObject.  You are likely familiar with the standard registry entries for COM classes.  But if not, let's take some time to review them.

reg1.gif (11933 bytes)

Figure 1

All component registry information is stored in the HKEY_CLASSES_ROOT hive.  MSComctlLib.Slider.2 is the ProgID of the Microsoft common control slider.  The subkey CLSID holds the registry-form unique identifier of the component.  MSComctlLib.Slider is the version independent ProgID of the control, and has subkey for the most recent ProgID of the component as well as the identifier of the class.

reg2.gif (17306 bytes)

Figure 2

Most of the COM registry action takes place in the HKEY_CLASSES_ROOT\CLSID hive.  Figure 2 shows the CLSID entry for the MSComctlLib.Slider.2 component of Figure 1.   While Figure 2 has a number of subkeys, only a few are used by the COM runtime (the others are used for application-specific purposes).  Subkeys ProgID and VersionIndependentProgID specify the ProgID and version independent ProgID: "MSComctlLib.Slider.2" and "MSComctlLib.Slider."  The COM runtime function ProgIDFromCLSID resolves the ProgID entry given the component's CLSID.

The TypeLib subkey identifies the LIBID of the type library in which the coclass is declared.  While a component's type library can be retrieved with the interface methods IProvideClassInfo::GetClassInfo and IDispatch::GetTypeInfo, it's sometimes helpful to be able to retrieve a component's type library without having to instantiate it.

By far the most important subkey in Figure 2 is the server key.  InprocServer32 identifies the server's filepath for DLL and OCX components, and LocalServer32 specifies EXE server filepaths.  These keys also have a ThreadingModel subkey with one of the following values: Apartment (for single-threaded apartment components), Free (for multi-threaded apartment components), Both (for components that run in either an STA or MTA), or no ThreadingModel subkey (which indicates that the component is to run only in the client's main STA).  

A complete list of meaningful CLSID subkeys can be found in the COM Fundamentals -> Reference -> Registry Entries -> CLSID Key section of the MSDN Library.

reg3.gif (14300 bytes)

Figure 3

Nested in HKEY_CLASSES_ROOT\TypeLib live the type library entries.  Figure 3 displays the type library registry entries for MSComctlLib.Slider.  The immediate subkey, 2.0, is the root of the version 2.0 type library hierarchy.  2.0 has three subkeys.  The first is the locale identifier (LCID) of the type library.  Zero is the system default LCID.   The LCID's subkey, win32, identifies the location of the type library (that is, the compiled IDL).  Notice that this key points to mscomctl.ocx, the same file as InprocServer32 in the previous figure.  Obviously the type library is not executable OCX (DLL) code.  Upon loading mscomctl.ocx, the COM runtime library checks the file's magic number (the magic number is the first four bytes of the file: an ASCII MSFT in the case of the type library, Microsoft's stock symbol).  If the magic number does identify a type library then all is well.  If the file isn't a type library, the COM runtime extracts the file's resource with resource ID 1.  This resource is the type library (you'll see how to add type libraries as resources in this article).  If you don't want to give your type library resource ID 1, or another resource already has that ID, you can append a comma followed by the resource ID to specify where the type library lives.  For example, mscomctl.ocx, 29 indicates that the type library is the resource of mscomctl.ocx with resource ID 29.

reg4.gif (14565 bytes)

Figure 4

COM interfaces can generally be marshalled over apartment, process, and machine boundaries.  The convienence of "location transparency" requires that a proxy/stub object exist for every remotable interface.  When an interface is marshalled to another apartment, a proxy object is built on the client side and a stub on the server side.   Marshaling is performed on a per-interface basis.  The COM runtime finds the entry of the interface to be marshalled in the HKEY_CLASSES_ROOT\Interface hive, as shown in Figure 4.  The GUID in Figure 4 is actually the interface identifier (IID) of the ISlider interface, the default interface of the COM object in the previous figures. 

reg5.gif (14565 bytes)

Figure 5

The important subkey of the Interface\{IID} key is ProxyStubClsid32.  This key specifies another COM class: the proxy/stub object.  This object is created by the COM runtime to marshal argument parameters across the wire.  We can look in the HKEY_CLASSES_ROOT\CLSID hive for the ProxyStubClsid32 value:

reg6.gif (13751 bytes)

Figure 6

In HKEY_CLASSES_ROOT\CLSID we find the location of our proxy/stub object.  Note that the default value of the CLSID key is PSOAInterface, literally "Proxy/Stub OLE Automation Interface."  Say "Hi" to your friend and mine, the Universal Marshaller.  All interfaces marked oleautomation (this is a MIDL attribute which mandates that the interface methods use only Automation-compatible parameters) which provide a type library can be marshalled by PSOAInterface.   This, of course, includes all dual and dispatch interfaces.  The Universal Marshaller helps keep the size of the project down and simplify distribution.

In this article you'll learn how to write self-registering DLL and EXE servers.  Visual C++ is our platform, and we'll be using raw C++ (yeah - like real men).  Make a new DLL project called "dlldemo1."  The first thing to do when writing a COM project (at least trivial COM projects such as this) is to define the interfaces in IDL.

vcidl1.gif (21122 bytes)

Figure 7

The Dlldemo1 component in our DLL server implements the interface IDlldemo1, as shown in Figure 7.  This interface exposes four methods which we will implement in the next Captain COM article.  Notice that the coclass and interface are defined inside a library clause.  MIDL will produce a type library (dlldemo1.tlb) which we'll link into our executable.  Our registration function will  not only register the Dlldemo1 ProgID and CLSID entries, but also the Interface and CLSID entries for the interface IDlldemo1 and the TypeLib entry for the type library Dlldemo1.  Notice that because IDlldemo1 has the oleautomation attribute, we can use the Universal Marshaller.

midldlg1.gif (14538 bytes)

Figure 8

MIDL compilation has become part of the Visual C++ build process.   By default Visual C++ will use the MkTypLib compatible (legacy) MIDL setting.  It will also forego generation of a header file defining the library's interfaces and .c file defining the library's GUIDs.  Specify an "Output header file name" to generate header and .c files, and uncheck the MkTypeLib box.

vcrgs1.gif (21276 bytes)

Figure 9

dlldemo1.rgs of Figure 8 is our ATL Registrar script.  The parser is found in the header file statreg.h.  The ATL Registrar script format is quite simple.  Each block is delimited with a pair of braces.  The name of each block is the name of the registry key that the block describes.  Every registry entry can be assigned a value with the '=' operator.   The type of the value is specified by a single-character code preceding the value: s for string, b for binary, or d for decimal DWORD. 

The first identifier in Figure 8 is the HKEY_CLASSES_ROOT symbol: HKCR.  Each hive is identified with a three or four letter symbol: HKCR for HKEY_CLASSES_ROOT, HKCU for HKEY_CURRENT_USER, HKLM for HKEY_LOCAL_MACHINE, HKU for HKEY_USERS, HKPD for HKEY_PERFORMANCE_DATA (not Hong Kong Police Department - sorry Jackie Chan), HKDD for HKEY_DYN_DATA, and HKCC for HKEY_CURRENT_CONFIG.

In dlldemo1.rgs, the HKEY_CLASSES_ROOT hive is opened and a CaptainCOM.dlldemo1.1 key added.  This is the component's ProgID key; notice the ForceRemove modifier.  When the script is executed, the ATL Registrar recursively removes each subkey in the ProgID key, then the ProgID key itself.  This ensures a clean registration; remnants of other registrations are removed.  All subkeys will be deleted unless they are marked with the NoRemove modifier.  Specifying a NoRemove subkey inside a ForceRemove key prevents the key from being removed, as the ATL Registrar will only remove keys that are free of subkeys.  The CLSID of the coclass (see dlldemo1.idl) is added to the Registrar file for both the ProgID and version-callindependent Prog ID.  The CurVer key specifies the current ProgID of the version-independent Prog ID.

HKEY_CLASSES_ROOT\CLSID is opened with the NoRemove modifier (this isn't necessary, as HKCR would need to be ForceRemove for the CLSID subkey to be removed, but it's nice to be explicit) and the coclass registry entry is created.  The ProgID and VersionIndependentProgID keys simply point back to the appropriate registry entries.  TypeLib holds the LIBID of the component's type library (which is produced when MIDL compiles dlldemo1.idl).   This LIBID is an index into HKEY_CLASSES_ROOT\TypeLib.  Notice that we don't have to write a script to register the type library key; the COM runtime provides a function to do that. 

InprocServer32 (along with it's brother LocalServer32) is the most important registry subkey in the world of COM.  It specifies where the COM runtime library can find the object's class factory.  All symbols inside a pair of % marks is called a Registar "replacement."  For example, %module% is the filepath replacement.  At runtime, the DLL can insert its filepath for the filepath replacement.  This is very important, as you don't have a clue where the DLL lives at compile time.  InprocServer32 has a single subkey: ThreadingModel.  We've covered this subkey a bit in the discussion of Figure 2.  Our sample component will live in a STA so ThreadingModel is assigned the value Apartment.  That's it for the registration script!  

demosrc1.gif (30005 bytes)

Figure 10

Figure 9 lists the C++ code for our stripped-down project.  Notice that there is no implementation of the Dlldemo1 coclass yet; we are just focusing on self-registration.  The module's entry point, DllMain, sets the global variable hInstance to the instance handle of the DLL.  This is necessary because GetModuleHandle(0) returns not the DLL handle but the process handle.

The project exports two functions: DllRegisterServer and DllUnregisterServer.  These are standard symbols recognized by regsvr32.exe, a number of installer programs, and the COM runtime function CoGetClassObjectFromURL.  Both export functions call GetFilePath to initialize the wide character array module with the DLL's filepath.  RegisterServer instantiates the ATL Registrar object (CRegObject), replaces the %Module% symbol with the DLL's filepath, and (un)registraters the .rgs resource. 

CRegObject::ResourceRegister only adds to the registry the keys that are specified in the Registrar script, of course.   Registration of the type library and interface proxy/stub is performed by the COM runtime library.  The DllRegisterServer implementation invokes LoadTypeLib to acquire an ITypeLib pointer to the type library.  RegisterTypeLib registers the type library key, plus all oleautomation-compatible interfaces defined in the IDL's library clause.  The function's second parameter is the type library's filepath (in this case, the filepath of the DLL, as the type library is the DLL's first resource).  Given a type library's LIBID and version number, the COM runtime function UnRegisterTypeLib undoes what RegisterTypeLib did.  It rips out the type library registry key and the library's interface keys.

That's all the C++ required to register this DLL COM server.  The ATL Registar class CRegObject has certainly saved us a lot of programming effort.  Let's finish up this project.

demosrc2.gif (13022 bytes)

Figure 11

A DLL (or OCX) server can express that it supports self-registration by including the OleSelfRegister flag in its version info resource.   Several pieces of code, including CoGetClassObjectFromURL, check the version info for OleSelfRegister before loading the DLL and invoking DllRegisterServer.  Note that you'll need to manually open the resource script file to add the flag; the version resource editor is pretty weak.

Insert a new .DEF file into your project and add the export symbols DllRegisterServer and DllUnregisterServer and you're ready to compile.  Run regsvr32.exe on your DLL (or click Tools->Register Control from the Visual C++ menu) to see the fruits of your labor.

demreg1.gif (11254 bytes)

Figure 12

The HKEY_CLASSES_ROOT hive holds (version-independent) ProgIDs.   We see that our ProgID, CaptainCOM.dlldemo1.1, and our version-independent ProgID, CaptainCOM.dlldemo, have both been successfully registered.  Giddyup!

demreg2.gif (15502 bytes)

Figure 13

Figure 12 displays the CLSID entry of our COM server.  TypeLib holds the GUID of the type library dlldemo1.  This is an index into the HKEY_CLASSES_ROOT\TypeLib key (see Figure 13).   InprocServer32 is the key of consequence.  Examination of its default value is proof that the replacement worked: the %Module% symbol in the Registrar script was replaced with the server's filepath.  The ThreadingModel value states that the CaptainCOM.dlldemo.1 component runs only in a single-threaded apartment.  Clients trying to access an instance of the component through the IDlldemo1 interface from other apartments must utilize the interface's proxy/stub (see Figure 14).

demreg3.gif (14240 bytes)

Figure 14

The type library registry entries are shown in Figure 13.  The default value of the Win32 key is our type library's filepath.   In DllRegisterServer we registered this filepath by passing it as RegisterTypeLib's second parameter.  The Win32 key can either point to a .TLB file (generated by MIDL) or a file whose first resource (the resource with ID 1) is the type library.  You can also specify files containing a type library that isn't the first resource by appending a comma followed by the resource ID to the filepath.

demreg4.gif (13566 bytes)

Figure 15

RegisterTypeLib also added the IDlldemo1 key to HKEY_CLASSES_ROOT\Interface.  The important subkey here is ProxyStubClsid32.   This is an index into the proxy/stub key of HKEY_CLASSES_ROOT\CLSID.  IDlldemo1's ProxyStubClsid32 default value references the Universal Marshaller PSOAInterface.  

All is well with COM: we have successfully registered a DLL server.  Now let's write an equivalent self-registering EXE server. 

The IDL file for the EXE project is very similar to the one listed in Figure 7.  The only difference is that our library is named Exedemo1, our coclass is named Exedemo1, and the interface it implements is IExedemo1.  The ATL Registrar script for the EXE server, exedemo1.rgs, is similar to that of Figure 9 with one important difference:

exesrc1.gif (20757 bytes)

Figure 16

Naturally, Figure 16 shows a LocalServer32 key instead of the InprocServer32 key found in DLLs.  That is not surprising.  What is surprising is the lack of a ThreadingModel subkey.  Recall that for a DLL component, the absence of the ThreadingModel subkey indicates that the component can run only in the process's main STA (that is, the first thread that calls CoInitialize(0) or CoInitializeEx(0, COINIT_APARTMENTTHREADED)).  So does CaptainCOM.Exedemo1.1's lack of a ThreadingModel key condemn it to run in the client's main STA?   Well, no.  Let's briefly review the mechanisms of component construction.

Let's say you have a DLL component that runs in an STA.  The client, which is running in an STA, calls CoCreateInstance to produce an instance of that component.  The COM runtime looks up the component's CLSID key in the registry and finds that ThreadingModel equals Apartment.   Because the client's thread model and the component's thread model match, the component is instantiated inside the client's apartment.  No proxy/stub is needed, and calls from the client are made on raw interface pointers.  It's fast and easy.

Now let's say that we are working with the same STA component in a DLL.  The client, however, runs from an MTA.  The client calls CoCreateInstance, and the COM runtime checks the registry: ThreadingModel is marked Apartment.  Dang.  We have incompatible threading models.  The COM runtime looks through the list of existing apartments for an STA.  If it finds one, the component is created in that STA and a proxy/stub pair is created (one that uses the STA's message loop for synchronization) and marshalled back to the client in the MTA.  If an STA doesn't already exist, COM makes a new thread, has it enter an STA, instantiates the component, and enters its a message loop.  The client in the MTA has an interface pointer not to the actual object, but to a proxy.  Invoking a method on that proxy posts a message to the message loop in the component's apartment.  The component's stub dispatches the message and calls the method on the actual object.  Relative to the client, this series of calls is done synchronously. 

Suppose your COM object is stored in a local server (that is, in an EXE).  The client runs in a different EXE than the local server in question.  The client calls CoCreateInstance to create an instance of the component in the local server.  The COM runtime looks for the server's CLSID key in the registry.  The filepath at LocalServer32 denotes where the component is stored.  If the local server is not already running, the COM runtime launches it.  When the local server is launched, execution begins at WinMain, which enters the main STA with a deft call to CoInitialize.  The local server creates an instance of the component's class factory (either on the heap or on the stack; it does not matter) and registers it with the API CoRegisterClassObject.  The class factory is a COM object that lives in the local server's main STA.  The COM runtime then pulls the pointer to the appropriate class factory off the process's registration table (the code to do this is not directly available to application programmers.  It is encapsulated by CoGetClassObject and the functions that wrap it) and invokes IClassFactory::CreateInstance.  This is where the local server philosophy diverges from  inproc server philosophy: the local server's IClassFactory::CreateInstance implementation can create the COM object in whichever apartment it wants!  In a DLL, IClassFactory::CreateInstance just creates the COM object in the current apartment (this is, the apartment in which IClassFactory::CreateInstance was invoked from).  This is due to efficiency concerns: horrendously slow thread-switching overhead from function calls won't be incurred when the object runs in the same apartment as the client.  If your DLL implementation of IClassFactory::CreateInstance creates the object in a different apartment than the one it is called from, you sentence the client to call methods on the object through the really slow inter-apartment proxy/stub pair (there are exceptions to this of course.  The object may aggregate the free-threaded marshaller.  But let's not go there - it's an entirely different can of worms).

But IClassFactory::CreateInstance in an EXE server is a different beast.  The guiding philosophy here is that the client is in a different process from the server, so a proxy/stub pair is necessary no matter what apartments the client and server run in.  In IClassFactory::CreateInstance, the local server can take certain actions to prevent bottlenecks.  Let's say that the IClassFactory::CreateInstance implementation simply operator news the requested COM object in the current apartment.  As the class factory was created in the main STA (the WinMain thread), all objects are also created in this thread.  The threading model is effectively main STA.  A bottleneck is inherent in this scheme: all access to the objects are serialized and all function calls require a process-switch.  If the object is requested to perform a lengthy operation, access to all other objects in the server are blocked until the operation is complete.  The object performing the operation will likely need to call objects in other processes (maybe it will need to fire event back at the client event through a sink).  These calls will be slow.  Interprocess calls are sluggish enough, and remote procedure calls (a local server can have a remote client) may take hundreds of milliseconds when invoked over the internet.  All the time that the system is waiting for an IPC or RPC call to complete, the rest of the apartment's objects remain blocked.  A massive bottleneck indeed.

An "STA thread pool" is a common solution to the aforementioned inefficiencies.  When invoked, IClassFactory::CreateIntstance spawns a new thread (or grabs an existing thread off a list) that immediately enters an STA.  A creation message is posted to this new thread's message queue.  The message pump pulls this creation message off the queue and instantiates the desired class with operator new (or a similar mechanism).  An interface pointer to this new object is then marshalled back to the class factory's apartment with a call to CoMarshalInterThreadInterfaceInStream.  Typically, the number of threads in the pool is a multiple of the number of processors on the machine (for example, the ATL thread allocator CComSimpleThreadAllocator creates four threads per processor).  Those who really want to optimize their server can implement IClassFactory::CreateInstance to instantiate components in an MTA thread pool.  Because components in MTAs don't have thread affinity, they'll take the next available MTA thread rather than waiting for the thread of their birth to become available.  The optimal number of threads in the pool is really dependent on the application: too many threads and time slices for a particular thread become too infrequent; too few threads and you bottleneck.  The type of application can help you decide the meanings of "too infrequent" and "bottleneck."

The moral of this story: the programmer, not COM, dictates the threading model for components in local servers.  You can even make this determination at runtime.  That's why there is no ThreadingModel value under LocalServer32.

exesrc2.gif (30790 bytes)

Figure 17

Figure 17 shows the C++ source required for a self-registering local server.  The local server's implementation of the RegisterServer function is identical to that of the inproc server's (see Figure 10).   It just delegates to the ATL Registrar method CRegObject::ResourceRegister for registration and CRegObject::ResourceUnregister for unregistration.

Unlike DLLs, EXEs don't export functions.  DllRegisterServer and DllUnregisterServer are the inprocess solution for self-registration.   The local server solution?  Command-line arguments.  The argument RegServer (case insensitive and prefixed with a slash or dash) tells the program to register itself and immediately return.  Likewise, UnregServer is the argument for unregistration. 

WinMain's third parameter points to the program's command-line arguments.  This string can also be retrieved with the Win32 API function GetCommandLineexedemo1.cpp begins by checking for one of the registration arguments in cmdLine.  On a match, the server retrieves its own filepath with GetModuleFileName, converts it to a UNICODE string, and calls RegisterServer.  It then registers or unregisters the type library as appropriate before returning.  Just add in the version info, type library, and Registrar script resources and build.   Executing "exedemo1 /RegServer" registers the server.

demreg5.gif (15153 bytes)

Figure 18

Hoorah.  Figure 18 shows the CLSID entry of the CaptainCOM.Exedemo1.1 coclass.  LocalServer32's default value is the server's filepath.  There is no ThreadingModel value, as the server itself determines the threading model for the component.

Well, now you are a self-registering expert.  Write a few of your own self-registering servers, and come back to read the next entry in the Journals of Captain COM!  We'll implement the dlldemo1 and exedemo1 coclasses.

May the COM be with you.

- Captain COM