Previous sections have illustrated how COM automatically loads DLLs to bring object implementations into the address space of client programs. What has not been discussed is exactly how and when these DLLs are unloaded. In general, server DLLs can prevent premature unloading, but it is the client that chooses the moment that DLLs are actually freed. Clients that wish to free idle DLLs call the COM API function CoFreeUnusedLibraries:
void CoFreeUnusedLibraries(void);
This routine is typically called by clients at idle time to garbage-collect their
address space. When CoFreeUnusedLibraries is called, COM queries each DLL that has been loaded to discover unneeded DLLs. It does this by calling each DLL’s DllCanUnloadNow function, which must be explicitly exported from the DLL.
The DllCanUnloadNow routine that each server DLL exports must conform to the following signature:
HRESULT DllCanUnloadNow(void);
If the DLL wishes to be freed, it returns S_OK. If the DLL wishes to remain loaded, it returns S_FALSE. Server DLLs must remain loaded at least as long as there are extant interface pointers to its objects. This means that the DLL will need to keep a count of all extant object references. To simplify this implementation, most DLLs keep a single lock count variable and use two functions to increment and decrement the lock count automatically:
LONG g_cLocks = 0;
void LockModule(void) { InterlockedIncrement(&g_cLocks); }
void UnlockModule(void) { InterlockedDecrement(&g_cLocks); }
Given the presence of these routines, the implementation of DllCanUnloadNow is extremely simple:
STDAPI DllCanUnloadNow(void)
{ return g_cLocks == 0 ? S_OK : S_FALSE; }
All that remains is to call the LockModule and UnlockModule routines at the appropriate time.
There are two basic forces that must keep a server DLL loaded: outstanding references to class instances and class objects and outstanding calls to IClassFactory::LockServer. Adding support for DllCanUnloadNow to class instances and class objects is fairly straightforward. Heap-based objects (such as class instances) simply increment the server lock count at the first call to AddRef:
STDMETHODIMP_(ULONG) Chimp::AddRef(void) {
if (m_cRef == 0)
LockModule();
return InterlockedIncrement(&m_cRef);
}
and decrement the server lock count at the final call to Release:
STDMETHODIMP_(ULONG) Chimp::Release(void) {
LONG res = InterlockedDecrement(&m_cRef);
if (res == 0) {
delete this;
UnlockModule();
}
return res;
}
Because non-heap-based objects (such as class objects) do not keep a reference count, each call to AddRef and Release should increment or decrement the lock count:
STDMETHODIMP_(ULONG) ChimpClass::AddRef(void) {
LockModule();
return 2;
}
STDMETHODIMP_(ULONG) ChimpClass::Release(void) {
UnlockModule();
return 1;
}
Class objects that implement IClassFactory should adjust their server’s lock count in calls to IClassFactory::LockServer:
STDMETHODIMP ChimpClass::LockServer(BOOL bLock) {
if (bLock)
LockModule();
else
UnlockModule();
return S_OK;
}
As discussed in Chapter 6, IClassFactory::LockServer exists primarily for out-of-process servers, but it is simple enough to implement in in-process servers.
It is worth noting that there is an inherent race condition in the CoFreeUnusedLibraries/DllCanUnloadNow protocol. It is possible that one thread may be executing the final release on the last instance exported from
a DLL while a second thread is simultaneously executing the CoFreeUnusedLibraries routine. COM takes every possible precaution to avoid this situation. In particular, the Windows NT 4.0 Service Pack 2 implementation of COM added a special facility to address this potential race condition. The Service Pack 2 version of the COM library detects that a server DLL has been accessed from multiple threads and, instead of unloading the DLL immediately from within CoFreeUnusedLibraries, COM enqueues the DLL onto a list of DLLs that need to be freed. COM will then wait an unspecified period of time before it will free these idle server DLLs to ensure that no residual Release calls are still being executed.16 This means that in multithreaded environments, it may take considerably longer for a DLL to unload from its client than is expected.
16 It is likely that Windows NT 5.0 will provide additional support for ensuring that DLLs are released promptly and safely. Consult the SDK documentation for more details.