Server Lifetime Management

[Previous] [Next]

DLL server lifetime is ultimately tied to the client that uses the objects it contains. When a client wants to remove a server from memory, it calls CoFreeUnusedLibraries, typically during idle processing. This results in the server receiving a call to the exported function DllCanUnloadNow. The server returns S_OK if the DLL can be freed, S_FALSE otherwise. The DLL can be freed if no outstanding references to objects are contained in the server (including class objects) and no outstanding locks are on the server via IClassFactory::LockServer. The implementation of DllCanUnloadNow, shown in the following code, reveals that CComModule maintains a global reference count that keeps the DLL loaded as long as it is nonzero.

 STDAPI DllCanUnloadNow(void) {     return (_Module.GetLockCount()==0) ? S_OK : S_FALSE; } 

The lock count is incremented and decremented using _Module.Lock and _Module.Unlock, which increment or decrement CComModule::m_nLockCnt. As you might expect, the lock count initially starts at 0. Typically, Lock is called when an object in the server is created and Unlock is called when it is destroyed. Class objects have slightly different behavior. Recall that the object map maintains an IUnknown pointer to the class objects it creates the first time they are requested. If construction of a class object incremented the lock count, the server would never be released. Thus class objects are created using CComObjectCached, which locks the server only when the object reference count reaches 2 and unlocks only when the count falls below 2. IClassFactory::LockServer also locks or unlocks the server, as implemented in CComClassFactory.

EXE servers have different requirements for shutting down. Class objects are registered with COM using CoRegisterClassObject. The SCM holds a reference to the class object until CoRevokeClassObject is called. ATL places a call to _Module.RevokeClassObjects at the end of an EXE server's _tWinMain entry point. Once again, the object map is iterated, this time to remove the class object references. RevokeClassObject is called on each map entry, which calls CoRevokeClassObject using the dwRegister cookie received from CoRegisterClassObject. When _Module.RevokeClassObjects is executed, the server is already shutting down, implying that class objects can't keep an EXE server running. That's why class objects in an ATL EXE server are created with CComObjectNoLock. The choice to use CComObjectNoLock in the typedef for _ClassFactoryCreatorClass is selected based on the type of server you're creating. The following code shows how the typedef is defined for DLL and EXE servers:

 #if defined(_WINDLL) | defined(_USRDLL) #define DECLARE_CLASSFACTORY_EX(cf) typedef CComCreator<\     CComObjectCached< cf > > _ClassFactoryCreatorClass; #else // Don't let class factory refcount influence lock count #define DECLARE_CLASSFACTORY_EX(cf) typedef CComCreator<\     CComObjectNoLock< cf > > _ClassFactoryCreatorClass; #endif 

As the name implies, CComObjectNoLock does no module locking whatsoever. That leaves outstanding object references and explicit IClassFactory:: LockServer locks to keep the server running. Once all objects have been released and the lock removed, the server can shut down. Because EXE servers run in their own process space, some signaling method must be used to initiate shutdown when the module lock count reaches 0. The ATL COM AppWizard provides the signaling mechanism by inserting a CComModule derived class in your application in stdafx.h. The derived class implements Unlock and adds some new members as well, shown here:

 class CExeModule : public CComModule { public:     LONG Unlock();     DWORD dwThreadID;     HANDLE hEventShutdown;     void MonitorShutdown();     bool StartMonitor();     bool bActivity; }; extern CExeModule _Module; 

Before starting the message pump, _Module.StartMonitor is called to create a Win32 event and start a worker thread. All of the related code is in the server main .cpp file, shown here:

 bool CExeModule::StartMonitor() {     hEventShutdown = CreateEvent(NULL, false, false, NULL);     if(hEventShutdown == NULL)         return false;     DWORD dwThreadID;     HANDLE h = CreateThread(NULL, 0, MonitorProc, this, 0,         &dwThreadID);     return (h != NULL); } 

The thread procedure MonitorProc begins executing immediately and delegates to MonitorShutdown, shown here:

 void CExeModule::MonitorShutdown() {     while (1)     {         WaitForSingleObject(hEventShutdown, INFINITE);         DWORD dwWait=0;         do         {             bActivity = false;             dwWait = WaitForSingleObject(hEventShutdown,                 dwTimeOut);         } while (dwWait == WAIT_OBJECT_0);         // Timed out         if(!bActivity && m_nLockCnt == 0)          { #if _WIN32_WINNT >= 0x0400 & defined(_ATL_FREE_THREADED)             CoSuspendClassObjects();             if(!bActivity && m_nLockCnt == 0) #endif                 break;         }     }     CloseHandle(hEventShutdown);     PostThreadMessage(dwThreadID, WM_QUIT, 0, 0); } 

This code is constantly running on the background thread, looking for the hEventShutdown event to be signaled. The event is signaled from the main thread when the module lock count reaches 0, as shown here:

 LONG CExeModule::Unlock() {     LONG l = CComModule::Unlock();     if(l == 0)     {         bActivity = true;         SetEvent(hEventShutdown);     }     return l; } 

After seeing the shutdown event and a period of time with no activity, the wait loop exits and a WM_QUIT message is posted to the main thread, causing the message loop to terminate. The _tWinMain function continues on to revoke the class objects before a graceful end.

Inside Atl
Inside ATL (Programming Languages/C)
ISBN: 1572318589
EAN: 2147483647
Year: 1998
Pages: 127 © 2008-2017.
If you may any questions please contact us: