Exposing the Class Factory

Now that we understand how to register a server module and what a class factory is, we can see how the server allows COM to access that class factory on behalf of calling clients. How this is accomplished is, in fact, the major difference between DLL and EXE servers, primarily because an EXE defines a task, whereas a DLL doesn't. A class factory is an object, and COM needs to obtain its IClassFactory or IClassFactory2 pointer, or whatever other pointer a client wants. (Clients can request any interface for a class factory.) Let's see how this works for each module in turn.

In-Process Server

Every DLL server must implement and export a function named DllGetClassObject with the following form:


STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv);

When a client asks COM to create an object and COM finds that an in-process server is available, COM will pull the DLL into memory with the COM API function CoLoadLibrary. COM then calls GetProcAddress looking for DllGetClassObject; if successful, it calls DllGetClassObject, passing the same CLSID and IID that the client passed to COM. This function creates a class factory for the CLSID and returns the appropriate interface pointer for the requested IID, usually IClassFactory or IClassFactory2, although the design of this function allows new interfaces to be used in the future. No straitjackets here.

DllGetClassObject is structurally similar to IClassFactory::CreateInstance, and as we'll see later in the sample code for this chapter, the two functions are almost identical: the difference is that CreateInstance creates the component's root object, whereas DllGetClassObject creates the class factory. Both query whatever object they create for the appropriate interface pointer to return, which conveniently calls AddRef as well.

Because DllGetClassObject is passed a CLSID, a single DLL server can provide different class factories for any number of different classes—that is, a single module can be the server for any number of component types. The OLE DLL is itself an example of such a server; it provides most of the internally used object classes of OLE from one DLL.


Be Sure to Export DllGetClassObject

When creating an in-process server or handle, be sure to export DllGetClassObject as well as DllCanUnloadNow. (See "Unloading Mechanisms" later in this chapter.) Failure to do so will cause a myriad of really strange and utterly confusing bugs. I guarantee that you'll hate tracking these bugs down. Save yourself the trouble and write yourself a really big, hot-pink fluorescent Post-it note and stick it in the middle of your monitor so you'll remember.


Local Server

Exposing a class factory from an EXE is somewhat different because an EXE has a WinMain, a message loop that defines its lifetime, and usually a main window. Whereas COM loads an in-process server and asks it for a class factory pointer, an EXE must instantiate all of its class factories on startup and register them with COM through the function CoRegisterClassObject—but only under the appropriate circumstances.

When COM launches an EXE, it appends the command-line argument -Embedding3 (case-insensitive) to the server path stored in the registry. This flag might also appear as /Embedding, and if you register your EXE with flags yourself, look for the flag at the end of the command line. Checking this flag should happen during initialization, after you've checked for -RegServer and -UnregServer. If this flag is not present, the end user has run your EXE stand-alone. If your component can't run stand-alone at all—that is, if it exists for no purpose other than to serve components—use this condition to immediately terminate the EXE. Note that if -Embedding is present, you should not show your main window because your component might be launched specifically to do some processing that the end user should not see. We'll see cases for which this really becomes important when we deal with OLE Documents.

When -Embedding is present, the server must create a separate class factory for each CLSID that it supports and pass the class factory interface pointers to CoRegisterClassObject. This works in the same way that we are accustomed to calling the Windows API function RegisterClass for each window that your application manages. With RegisterClass, you create a WNDCLASS structure, fill in the lpfnWndProc field with a pointer to your window's message procedure, and pass a pointer to that WNDCLASS to RegisterClass. Your window procedure is not actually called until something creates a window of that class. With CoRegisterClassObject, you create each class factory you support and pass an interface pointer from each to CoRegisterClassObject along with its associated CLSID, but those objects aren't used until some external client asks COM for an object of that CLSID.4 If the EXE was launched because a client has already asked COM for such an object, COM is patiently waiting (for up to 5 to 10 minutes or so) for the server to register an appropriate class factory before returning to the client with a time-out error. Therefore, you should create and register class factories as soon as possible in your server, particularly before entering your message loop.

Do note that you need to register all supported class factories because, unlike DllGetClassObject, COM doesn't have a way to pass an EXE the desired CLSID, and in some cases a single server really has to register multiple factories at once. To see why, we need to look at the CoRegisterClassObject arguments:

Argument

Description

rclsid

The CLSID associated with the class factory

pUnk

The IUnknown pointer to the class factory object

dwContext

A DWORD describing the execution context of this class factory—that is, the type of server that is registering the factory, which can be CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER

dwUsage

A DWORD describing how many objects this class factory can create

pdwReg

A pointer to a DWORD that receives a registration "key" to use when revoking the registration


The dwUsage flag explains why a server might need to register all of its factories at once: any class factory registered with dwUsage set to REGCLS_MULTIPLE_USE eliminates the need to launch another instance of the same EXE every time a client needs one of its components. If an EXE is launched once for one CLSID, it makes all of its other components available as well, and this increases overall performance. On the other hand, a single-use class factory registered with REGCLS_SINGLE_USE can be used only once, and COM must launch another instance of the server to get another instance of that component, which is much costlier. This is necessary when the component involves singular resources or a user interface that excludes multiple object instances.

When a local server registers a class factory with CLSCTX_LOCAL_SERVER and REGCLS_MULTIPLE_USE, that same class factory is also registered with CLSCTX_INPROC_SERVER. This makes the same class factory available to everything else in that process, preventing the need to launch another instance of the EXE. To suppress this behavior, use the REGCLS_MULTI_SEPARATE flag in dwUsage, which registers the class factory as strictly local.

During shutdown, a local server must reverse each call to CoRegisterClassObject by passing the value stored in the pdwReg argument to CoRevokeClassObject. This function tells COM to take the previously registered class factory out of service and release any reference counts to it. This will clean up the class factory object (which should delete itself when its reference count reaches zero, like most other objects) and ensure that no client can gain access to a (now) invalid class factory.

Finally, note that it is altogether allowable for an in-process server to register a class factory just as a local server does. This allows other clients to access objects of that class without having to load another DLL or launch another EXE. However, a client in another process will see the server as a local server because the server DLL is not loaded again into the other client's address space. There are some complications with this technique. The biggest one is that the DLL needs to keep the process in which it is loaded alive until all other processes have disconnected from it. This is tricky if not outright impossible at times.

3 A leftover from OLE 1, in which servers did nothing more than provide an embedded compound document object.

4 COM itself will actually call AddRef a few times when it saves your pointer internally.