Unloading Mechanisms

Just as COM will load DLLs and launch EXEs programmatically without any user interaction, it must also have a way to unload those DLLs and terminate those EXEs. Otherwise, it would, over time fill, all available memory with servers that are no longer in use. The problem is a matter of how COM determines whether or not a server is currently servicing any objects.

The bottom line is that a server is no longer needed when there are no lock counts from IClassFactory::LockServer and there are no objects, excluding the class factory objects, currently being serviced. Typically, a server will maintain one or two global variables (an object count and a lock count, or one combined counter), and when both counts go to 0, the server isn't needed anymore. An EXE actually doesn't need COM to unload: it can detect the appropriate conditions and terminate itself by calling PostQuitMessage to exit its message loop. But DLLs have no idea of how to "quit" or unload themselves, so they must mark themselves as "unloadable" and wait for COM to ask for that condition. Let's look at each case in turn.

In-Process Server

Being rather passive, the DLL unloading mechanism is fairly trivial. Every now and then—primarily when a client calls the function CoFreeUnusedLibraries—COM attempts to call an exported function named DllCanUnloadNow in the same manner that it calls DllGetClassObject. This function takes no arguments and returns an HRESULT:


STDAPI DllCanUnloadNow(void)

When COM calls this function, it is essentially asking "Can I unload you now?" DllCanUnloadNow returns S_FALSE if any objects or any locks exist, in which case COM doesn't do anything more. If there are no locks or objects, the function returns NOERROR (or S_OK), and COM follows by calling CoFreeLibrary to reverse the CoLoadLibrary function call that COM used to load the DLL in the first place.

Note: Early 16-bit versions of OLE did not implement the CoFreeUnusedLibraries, so DllCanUnloadNow was never called. This led to the belief that it was not necessary to implement this function or IClassFactory::LockServer because the DLL would stay in memory for the duration of the client process. This is no longer true because the COM function is fully functional and will call all DLLs in response. To support proper unloading, you must always implement IClassFactory::LockServer as well as DllCanUnloadNow.

A DLL's response to a DllCanUnloadNow call does not take into consideration the existence or reference counts on any class factory object. Such references are not sufficient to keep a server in memory, which is why LockServer exists. To be perfectly honest, an in-process server could include a count of class factories in its global object count if it wanted to, but this doesn't work with a local server, as the following section illustrates.

Local Server

Unlike a DLL server, which waits passively for COM to ask about unloading it, the EXE server must watch actively for the following conditions and terminate itself when those conditions are met:

If a single combined counter is used, the server need only detect when it is decremented to 0. All of these conditions imply active detection of a decrement event, not just a zero condition. The server terminates after the final decrement, not whenever a counter happens to be 0. Otherwise, the server would start up, detect a zero count, and terminate. That sort of code is about as useful as wearing dark sunglasses for stargazing.

In addition, if an EXE server involves any kind of user interface, it must also maintain a condition called user control. This Boolean flag is normally FALSE but is set to TRUE if an end user does anything that "takes control" of the server. For example, a server with a Multiple Document Interface (MDI) might be servicing a document object for the purposes of another client. The end user might come along and create a new document in that server as if it had been run stand-alone. In such a case, the server should most definitely not shut itself down when the original document object is released; otherwise, the end user's new document would be prematurely destroyed without any chance to save it. Bad idea. Automatic termination should not occur unless the user control flag is FALSE. If it is TRUE, only the end user can shut down that EXE.

When an end user forcibly closes a server, other running objects may still be in service. In such a case, the server has two choices: it can either hide its main window, giving the end user the illusion of closing by not actually terminating until all objects are released (essentially setting the user control flag back to FALSE), or disconnect each of its running objects by calling the COM API function CoDisconnectObject. This latter solution is rather rude because it will cause any external client's interface calls to return the error RPC_E_NOTCONNECTED—the client can still make the call because it still has a pointer to the interface proxy, but that proxy has been disconnected from its stub. In effect, CoDisconnectObject causes a controlled crash of the external connections to the object. Brutal as this may be, CoDisconnectObject gives a server control over external reference counts so it can properly free its objects on shutdown.

We can now examine why a reference count on a class factory object is not strong enough to keep an EXE server from terminating itself. Recall that you must create class factory objects during initialization and register them with CoRegisterClassObject. In the process of registration, there will be at least one, if not more, calls to that object's AddRef function. The matching calls to Release will not be made, however, until the server calls CoRevokeClassObject during shutdown. If a positive reference count could keep a class factory in memory, the EXE could not terminate until that object's reference count reached zero and it was destroyed. But the reference count will never reach zero unless we call CoRevokeClassObject, but we call CoRevokeClassObject only when we are in the shutdown process and are on the nonstop express to oblivion. We can't revoke until we're shutting down and we can't shut down until we revoke. Aaaugh! Fourth down and 100 yards to go…so we punt. Officially, a positive reference count on a class factory cannot be used to keep a server in memory. If a client wants to hold a class factory outside the scope of a function, it must rely on LockServer to prevent server shutdown. This is one of the few special cases of reference counting in all of OLE.