//----------------------------------------------------------------------------- // // // File: // aqueue.cpp // Description: // Implementation of DLL Exports. // Author: Mike Swafford (MikeSwa) // // History: // // Copyright (C) 1998 Microsoft Corporation // //----------------------------------------------------------------------------- #include "aqprecmp.h" #ifndef PLATINUM #include "initguid.h" #include #endif //PLATINUM #include "aqueue_i.c" #include "aqintrnl_i.c" #include "SMTPConn.h" #include "qwiklist.h" #include "fifoqimp.h" #include #include #include #include #include #include #include "aqrpcsvr.h" //Global vars used for shutdown DWORD g_cInstances = 0; CShareLockNH g_slInit; //lock used for thread-safe initialization //Global vars used for Dll init/shutdown (including Cat COM stuff) LONG g_cDllInit = 0; BOOL g_fInit = FALSE; CShareLockNH g_slDllInit; BOOL g_fForceDllCanUnloadNowFailure = FALSE; #define CALL_SERVICE_STATUS_CALLBACK \ pServiceStatusFn ? pServiceStatusFn(pvServiceContext) : 0 // SEO crap needed for aqdisp #define _ATL_NO_DEBUG_CRT #define _ASSERTE _ASSERT #define _WINDLL #include "atlbase.h" extern CComModule _Module; #include "atlcom.h" #undef _WINDLL CComModule _Module; BEGIN_OBJECT_MAP(ObjectMap) END_OBJECT_MAP() #include DEBUG_PRINTS *g_pDebug = NULL; //---[ HrAdvQueueInitializeEx ]------------------------------------------------- // // // Description: // Aqueue.dll initialization function that provides in params for user name, // domain, password, and service control callback functions. // Parameters: // IN pISMTPServer ptr to local delivery function / object // IN dwServerInstance virtual server instance // IN szUserName User name to log on DS with // IN szDomainName Domain name to log on to DS with // IN szPassword Password to authenticate to DS with // IN pServiceStatusFn Server status callback function // IN pvServiceContext Context to pass back for callback function // OUT ppIAdvQueue returned IAdvQueue ptr // OUT ppIConnectionManager returned IConnectionManager ptr // OUT ppIAdvQueueConfig returned IAdvQueueConfig ptr // OUT ppvContext Virtual server context // Returns: // // //----------------------------------------------------------------------------- HRESULT HrAdvQueueInitializeEx( IN ISMTPServer *pISMTPServer, IN DWORD dwServerInstance, IN LPSTR szUserName, IN LPSTR szDomainName, IN LPSTR szPassword, IN PSRVFN pServiceStatusFn, IN PVOID pvServiceContext, OUT IAdvQueue **ppIAdvQueue, OUT IConnectionManager **ppIConnectionManager, OUT IAdvQueueConfig **ppIAdvQueueConfig, OUT PVOID *ppvContext) { TraceFunctEnterEx((LPARAM) NULL, "HrAdvQueueInitialize"); HRESULT hr = S_OK; CAQSvrInst *paqinst = NULL; CDomainMappingTable *pdmt = NULL; BOOL fLocked = FALSE; BOOL fInstanceCounted = FALSE; #ifdef PLATINUM BOOL fIisRtlInit = FALSE; BOOL fATQInit = FALSE; #endif BOOL fAQDllInit = FALSE; BOOL fExchmemInit = FALSE; BOOL fCPoolInit = FALSE; BOOL fRpcInit = FALSE; BOOL fDSNInit = FALSE; CALL_SERVICE_STATUS_CALLBACK; g_slInit.ExclusiveLock(); fLocked = TRUE; if ((NULL == ppIAdvQueue) || (NULL == ppIConnectionManager) || (NULL == ppvContext) || (NULL == ppIAdvQueueConfig)) { hr = E_INVALIDARG; goto Exit; } *ppvContext = NULL; // // Update global config information. // ReadGlobalRegistryConfiguration(); if (1 == InterlockedIncrement((PLONG) &g_cInstances)) { fInstanceCounted = TRUE; CALL_SERVICE_STATUS_CALLBACK; #ifdef PLATINUM //Initialize IISRTL if (!InitializeIISRTL()) { hr = HRESULT_FROM_WIN32(GetLastError()); ErrorTrace((LPARAM) NULL, "ERROR: LISRTL Init failed with 0x%08X", hr); if (SUCCEEDED(hr)) hr = E_FAIL; goto Exit; } fIisRtlInit = TRUE; //Initialize ATQ if (!AtqInitialize(0)) { hr = HRESULT_FROM_WIN32(GetLastError()); ErrorTrace((LPARAM) NULL, "ERROR: ATQ Init failed with 0x%08X", hr); if (SUCCEEDED(hr)) hr = E_FAIL; goto Exit; } fATQInit = TRUE; #endif hr = HrDllInitialize(); if (FAILED(hr)) { goto Exit; } fAQDllInit = TRUE; //create CPool objects if (!CQuickList::s_QuickListPool.ReserveMemory(10000, sizeof(CQuickList))) hr = E_OUTOFMEMORY; if (!CSMTPConn::s_SMTPConnPool.ReserveMemory(g_cMaxConnections, sizeof(CSMTPConn))) hr = E_OUTOFMEMORY; if (!CMsgRef::s_MsgRefPool.ReserveMemory(g_cMaxMsgObjects, MSGREF_STANDARD_CPOOL_SIZE)) hr = E_OUTOFMEMORY; if (!CAQMsgGuidListEntry::s_MsgGuidListEntryPool.ReserveMemory(500, sizeof(CAQMsgGuidListEntry))) hr = E_OUTOFMEMORY; if (!CAsyncWorkQueueItem::s_CAsyncWorkQueueItemPool.ReserveMemory(20000, sizeof(CAsyncWorkQueueItemAllocatorBlock))) hr = E_OUTOFMEMORY; if (!CAddr::Pool.ReserveMemory(1000, sizeof(CAddr))) hr = E_OUTOFMEMORY; if (!CAQSvrInst::CAQLocalDeliveryNotify::s_pool.ReserveMemory(g_cMaxPendingLocal, sizeof(CAQSvrInst::CAQLocalDeliveryNotify))) hr = E_OUTOFMEMORY; if (!CBlockMemoryAccess::m_Pool.ReserveMemory(2000, sizeof(BLOCK_HEAP_NODE))) hr = E_OUTOFMEMORY; if (FAILED(hr)) { ErrorTrace((LPARAM) NULL, "Error unable to initialize CPOOL"); goto Exit; } fCPoolInit = TRUE; hr = CDSNGenerator::HrStaticInit(); if(FAILED(hr)) { ErrorTrace((LPARAM) NULL, "CDSNGenerator::StaticInif failed hr %08lx", hr); goto Exit; } fDSNInit = TRUE; //Initialize Queue Admin RPC interface hr = CAQRpcSvrInst::HrInitializeAQRpc(); if (FAILED(hr)) goto Exit; fRpcInit = TRUE; } if (!g_pslGlobals) { g_pslGlobals = new CShareLockNH(); if (NULL == g_pslGlobals) { hr = E_OUTOFMEMORY; goto Exit; } } CALL_SERVICE_STATUS_CALLBACK; g_slInit.ExclusiveUnlock(); fLocked = FALSE; CFifoQueue::StaticInit(); CFifoQueue::StaticInit(); CFifoQueue::StaticInit(); CFifoQueue::StaticInit(); //Create requested objects CALL_SERVICE_STATUS_CALLBACK; paqinst = new CAQSvrInst(dwServerInstance, pISMTPServer); if (NULL == paqinst) { hr = E_OUTOFMEMORY; goto Exit; } CALL_SERVICE_STATUS_CALLBACK; hr = paqinst->HrInitialize(szUserName, szDomainName, szPassword, pServiceStatusFn, pvServiceContext); if (FAILED(hr)) goto Exit; //Create Connection Manager CALL_SERVICE_STATUS_CALLBACK; hr = paqinst->HrGetIConnectionManager(ppIConnectionManager); //Set Return values *ppIAdvQueue = (IAdvQueue *) paqinst; //Already addref'd at creation *ppIAdvQueueConfig = (IAdvQueueConfig *) paqinst; (*ppIAdvQueueConfig)->AddRef(); Exit: if (FAILED(hr)) { //Make sure that we clean up everything here if (NULL != paqinst) paqinst->Release(); //If initialization failed... we should not count an //instance as started if (fInstanceCounted) InterlockedDecrement((PLONG) &g_cInstances); #ifdef PLATINUM if (fATQInit) AtqTerminate(); if (fIisRtlInit) TerminateIISRTL(); #endif if (fAQDllInit) DllDeinitialize(); if (fCPoolInit) { //Release CPool objects CAQSvrInst::CAQLocalDeliveryNotify::s_pool.ReleaseMemory(); CAddr::Pool.ReleaseMemory(); CQuickList::s_QuickListPool.ReleaseMemory(); CSMTPConn::s_SMTPConnPool.ReleaseMemory(); CMsgRef::s_MsgRefPool.ReleaseMemory(); CAQMsgGuidListEntry::s_MsgGuidListEntryPool.ReleaseMemory(); CAsyncWorkQueueItem::s_CAsyncWorkQueueItemPool.ReleaseMemory(); CBlockMemoryAccess::m_Pool.ReleaseMemory(); } if (fDSNInit) CDSNGenerator::StaticDeinit(); if (fRpcInit) CAQRpcSvrInst::HrDeinitializeAQRpc(); } else { *ppvContext = (PVOID) paqinst; paqinst->AddRef(); } if (fLocked) g_slInit.ExclusiveUnlock(); TraceFunctLeave(); return hr; } //---[ HrAdvQueueInitialize ]--------------------------------------------------- // // // Description: // Performs DLL-wide initialization // // Parameters: // IN pISMTPServer ptr to local delivery function / object // IN dwServerInstance virtual server instance // OUT ppIAdvQueue returned IAdvQueue ptr // OUT ppIConnectionManager returned IConnectionManager ptr // OUT ppIAdvQueueConfig returned IAdvQueueConfig ptr // OUT ppvContext Virtual server context // Returns: // S_OK on success // //----------------------------------------------------------------------------- HRESULT HrAdvQueueInitialize( IN ISMTPServer *pISMTPServer, IN DWORD dwServerInstance, OUT IAdvQueue **ppIAdvQueue, OUT IConnectionManager **ppIConnectionManager, OUT IAdvQueueConfig **ppIAdvQueueConfig, OUT PVOID *ppvContext) { HRESULT hr = S_OK; hr = HrAdvQueueInitializeEx(pISMTPServer, dwServerInstance, NULL, NULL, NULL, NULL, NULL, ppIAdvQueue, ppIConnectionManager, ppIAdvQueueConfig, ppvContext); return hr; } //---[ HrAdvQueueDeinitializeEx ]------------------------------------------------ // // // Description: // Performs DLL-wide Cleanup. // // Adds callback to service control manager. // // This MUST not be called until all DLL objects have been released. // // NOTE: There are several objects that are exported outside this DLL. // The following are directly exported & should be released before the // the Heap and CPool allocations are freed // IAdvQueue // IConnectionManager // ISMTPConnection // The Message Context also contains several references to internal objects, // but does not need to be explicitly released (since these objects can only // be accessed though the AckMessage() call). // Parameters: // PVOID pvContext Context that was returned by initialization // function // IN pServiceStatusFn Server status callback function // IN pvServiceContext Context to pass back for callback function // Returns: // S_OK on success // //----------------------------------------------------------------------------- HRESULT HrAdvQueueDeinitializeEx(IN PVOID pvContext, IN PSRVFN pServiceStatusFn, IN PVOID pvServiceContext) { TraceFunctEnterEx((LPARAM) NULL, "HrAdvQueueDeinitialize"); HRESULT hr = S_OK; HRESULT hrCurrent = S_OK; DWORD cRefs; DWORD dwWaitResult = WAIT_OBJECT_0; bool fDestroyHeap = true; DWORD dwShutdownTimeout = 0; //time to wait for shutdown CAQSvrInst *paqinst; g_fForceDllCanUnloadNowFailure = TRUE; g_slInit.ExclusiveLock(); if (NULL != pvContext) { paqinst = (CAQSvrInst *) pvContext; hr = paqinst->HrDeinitialize(); cRefs = paqinst->Release(); DebugTrace((LPARAM) NULL, "There are %d refs remaining on the CMQ", cRefs); if (0 != cRefs) { _ASSERT(0 && "Someone has outstanding references to IAdvQueue or IAdvQueuConfig"); fDestroyHeap = false; } } CFifoQueue::StaticDeinit(); CFifoQueue::StaticDeinit(); CFifoQueue::StaticDeinit(); CFifoQueue::StaticDeinit(); if (0 == InterlockedDecrement((PLONG) &g_cInstances)) { #ifdef PLATINUM AtqTerminate(); #endif if (fDestroyHeap) { delete g_pslGlobals; g_pslGlobals = NULL; DllDeinitialize(); //Release CPool objects CAQSvrInst::CAQLocalDeliveryNotify::s_pool.ReleaseMemory(); CAddr::Pool.ReleaseMemory(); CQuickList::s_QuickListPool.ReleaseMemory(); CSMTPConn::s_SMTPConnPool.ReleaseMemory(); CMsgRef::s_MsgRefPool.ReleaseMemory(); CAQMsgGuidListEntry::s_MsgGuidListEntryPool.ReleaseMemory(); CAsyncWorkQueueItem::s_CAsyncWorkQueueItemPool.ReleaseMemory(); CBlockMemoryAccess::m_Pool.ReleaseMemory(); } // // Deinit DSN Generator // CDSNGenerator::StaticDeinit(); //Deinitialize Queue Admin RPC interface hr = CAQRpcSvrInst::HrDeinitializeAQRpc(); #ifdef PLATINUM TerminateIISRTL(); #endif //Force mailmsg and other COM DLLs to go buh-bye CoFreeUnusedLibraries(); } g_slInit.ExclusiveUnlock(); TraceFunctLeave(); g_fForceDllCanUnloadNowFailure = FALSE; return hr; } //---[ HrAdvQueueDeinitialize ]------------------------------------------------ // // // Description: // Performs DLL-wide Cleanup. // // This MUST not be called until all DLL objects have been released. // // NOTE: There are several objects that are exported outside this DLL. // The following are directly exported & should be released before the // the Heap and CPool allocations are freed // IAdvQueue // IConnectionManager // ISMTPConnection // The Message Context also contains several references to internal objects, // but does not need to be explicitly released (since these objects can only // be accessed though the AckMessage() call). // Parameters: // PVOID pvContext Context that was returned by initialization // function // Returns: // S_OK on success // //----------------------------------------------------------------------------- HRESULT HrAdvQueueDeinitialize(PVOID pvContext) { return HrAdvQueueDeinitializeEx(pvContext, NULL, NULL); } //---[ HrRegisterAdvQueueDll ]------------------------------------------------- // // // Description: // Sets metabase path of for advanced queuing DLL to this DLL. // Parameters: // hAQInstance - Handle passed into DLL main // Returns: // S_OK on success // E_INVALIDARG if hAQInstance is NULL. // Error codes from accessed metabase // History: // 7/30/99 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT HrRegisterAdvQueueDll(HMODULE hAQInstance) { HRESULT hr = S_OK; WCHAR wszModule[512] = L""; METADATA_HANDLE hMDRootVS = NULL; METADATA_RECORD mdrData; DWORD dwErr = NO_ERROR; DWORD cbModule = 0; IMSAdminBase *pMSAdmin = NULL; ZeroMemory(&mdrData, sizeof(METADATA_RECORD)); CoInitialize(NULL); InitAsyncTrace(); TraceFunctEnterEx((LPARAM) NULL, "HrRegisterAdvQueueDll"); if (!hAQInstance) { hr = E_INVALIDARG; ErrorTrace((LPARAM) NULL, "DLL Main did not save instance"); goto Exit; } hr = CoCreateInstance(CLSID_MSAdminBase,NULL,CLSCTX_ALL,IID_IMSAdminBase,(void **) &pMSAdmin); if (FAILED(hr)) { ErrorTrace((LPARAM) NULL, "CoCreateInstance failed! hr = 0x%08X", hr); goto Exit; } dwErr = GetModuleFileNameW(hAQInstance, wszModule, sizeof(wszModule)/sizeof(WCHAR)); //GetModuleFileName returns non-zero on success if (0 == dwErr) { hr = HRESULT_FROM_WIN32(GetLastError()); ErrorTrace((LPARAM) NULL, "GetModule name failed - 0x%08X", hr); if (SUCCEEDED(hr)) hr = E_FAIL; goto Exit; } cbModule = (wcslen(wszModule)+1)*sizeof(WCHAR); hr = pMSAdmin->OpenKey( METADATA_MASTER_ROOT_HANDLE, L"LM/SMTPSVC/", METADATA_PERMISSION_READ | METADATA_PERMISSION_WRITE, 10000, &hMDRootVS); if (FAILED(hr)) { ErrorTrace((LPARAM) NULL, "Could not open the key! - 0x%08x", hr); goto Exit; } mdrData.dwMDIdentifier = MD_AQUEUE_DLL; mdrData.dwMDAttributes = METADATA_INHERIT; mdrData.dwMDUserType = IIS_MD_UT_SERVER; mdrData.dwMDDataType = STRING_METADATA; mdrData.dwMDDataLen = cbModule; mdrData.pbMDData = (PBYTE) wszModule; mdrData.dwMDDataTag = 0; hr = pMSAdmin->SetData( hMDRootVS, L"", &mdrData); if (FAILED(hr)) { ErrorTrace((LPARAM) NULL, "Could set the AQ DLL - 0x%08X", hr); goto Exit; } Exit: if (NULL != hMDRootVS) pMSAdmin->CloseKey(hMDRootVS); if (pMSAdmin) { hr = pMSAdmin->SaveData(); if (FAILED(hr)) { ErrorTrace((LPARAM) NULL, "Error saving metabase data - 0x%08X", hr); } pMSAdmin->Release(); } TraceFunctLeave(); TermAsyncTrace(); CoUninitialize(); return hr; } //---[ HrUnregisterAdvQueueDll ]----------------------------------------------- // // // Description: // Removes the AdvQueue DLL setting from the metabase // Parameters: // - // Returns: // S_OK on success // Error from MSAdminBase // History: // 8/2/99 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT HrUnregisterAdvQueueDll() { HRESULT hr = S_OK; DWORD dwErr = NO_ERROR; METADATA_HANDLE hMDRootVS = NULL; IMSAdminBase *pMSAdmin = NULL; CoInitialize(NULL); InitAsyncTrace(); TraceFunctEnterEx((LPARAM) NULL, "HrUnregisterAdvQueueDll"); hr = CoCreateInstance(CLSID_MSAdminBase,NULL,CLSCTX_ALL,IID_IMSAdminBase,(void **) &pMSAdmin); if (FAILED(hr)) { ErrorTrace((LPARAM) NULL, "CoCreateInstance failed! hr = 0x%08X", hr); goto Exit; } hr = pMSAdmin->OpenKey( METADATA_MASTER_ROOT_HANDLE, L"LM/SMTPSVC/", METADATA_PERMISSION_READ | METADATA_PERMISSION_WRITE, 10000, &hMDRootVS); if (FAILED(hr)) { ErrorTrace((LPARAM) NULL, "Could not open the key! - 0x%08x", hr); goto Exit; } hr = pMSAdmin->DeleteData( hMDRootVS, L"", MD_AQUEUE_DLL, STRING_METADATA); if (FAILED(hr)) { ErrorTrace((LPARAM) NULL, "Could delete the AQ DLL - 0x%08X", hr); goto Exit; } Exit: if (NULL != hMDRootVS) pMSAdmin->CloseKey(hMDRootVS); if (pMSAdmin) { hr = pMSAdmin->SaveData(); if (FAILED(hr)) { ErrorTrace((LPARAM) NULL, "Error saving metabase data - 0x%08X", hr); } pMSAdmin->Release(); } TraceFunctLeave(); TermAsyncTrace(); CoUninitialize(); return hr; } ///////////////////////////////////////////////////////////////////////////// // DLL Entry Point extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/) { if (dwReason == DLL_PROCESS_ATTACH) { g_hAQInstance = hInstance; _Module.Init(ObjectMap, hInstance); DisableThreadLibraryCalls(hInstance); } else if (dwReason == DLL_PROCESS_DETACH) { _Module.Term(); } return CatDllMain(hInstance, dwReason, NULL); // ok } ///////////////////////////////////////////////////////////////////////////// // DLL Entry Point // // Register COM objects // STDAPI DllRegisterServer() { HRESULT hr = S_OK; HRESULT hrCat = S_OK; hr = HrRegisterAdvQueueDll(g_hAQInstance); hrCat = RegisterCatServer(); if (SUCCEEDED(hr)) hr = hrCat; return hr; } // // Unregister COM objects // STDAPI DllUnregisterServer() { HRESULT hr = S_OK; HRESULT hrCat = S_OK; hr = HrUnregisterAdvQueueDll(); hrCat = UnregisterCatServer(); if (SUCCEEDED(hr)) hr = hrCat; return hr; } STDAPI DllCanUnloadNow() { HRESULT hr; hr = DllCanUnloadCatNow(); if(hr == S_OK) { // // Check aqueue COM objects (if any) // if (g_fForceDllCanUnloadNowFailure || g_cInstances) hr = S_FALSE; } return hr; } STDAPI DllGetClassObject( const CLSID& clsid, const IID& iid, void** ppv) { HRESULT hr; // // Check to see if clsid is an aqueue object (if any aqueue // objects are cocreateable) // Currently none are // // Pass to the cat // hr = DllGetCatClassObject( clsid, iid, ppv); return hr; } //+------------------------------------------------------------ // // Function: HrDllInitialize // // Synopsis: Refcounted initialize of exchmem and tracing // The logic for HrDllInitialize and DllDeInitialize depend on the // facts that the callers always call HrDllInitialize first and only // call DllDeInitialize once after each call to HrDllInitialize succeeds // // Arguments: NONE // // Returns: // S_OK: Success // E_OUTOFMEMORY // error from exstrace // // History: // jstamerj 1998/12/16 15:37:07: Created. // //------------------------------------------------------------- HRESULT HrDllInitialize() { HRESULT hr = S_OK; LONG lNewCount; // // Increment inside a sharelock because of the following case: // If multiple threads are calling initialize and one thread is // actually doing the initialization, we don't want any threads to // return from this function until the initialization is done // g_slDllInit.ShareLock(); lNewCount = InterlockedIncrement(&g_cDllInit); // // No matter what, we must Init before leaving this call // Possible scenerios: // // lNewCount = 1, g_fInit = FALSE // Normal initialization case // lNewCount = 1, g_fInit = TRUE // Another thread is in DllDeinitialize and we have a race to // see who gets the exclusive lock first. If we get it first, // DllInitialize will do nothing (since g_fInit is TRUE) and // DllDeInitialize will do nothing (since g_cDllInit will be > // 0) // If DllDeInitialize gets the exclusive lock first, it will // deinit and we will reinit // lNewCount > 1, g_fInit = FALSE // We need to get the exclusive lock to init (or to wait until // another thread inits) // lNewCount > 1, g_fInit = TRUE // We're alrady initialized, continue. // if((lNewCount == 1) || (g_fInit == FALSE)) { g_slDllInit.ShareUnlock(); g_slDllInit.ExclusiveLock(); if(g_fInit == FALSE) { // // Initialize exchmem and tracing // InitAsyncTrace(); // // Initialize exchmem // if(!TrHeapCreate()) { hr = E_OUTOFMEMORY; TermAsyncTrace(); } if(SUCCEEDED(hr)) { g_fInit = TRUE; } else { InterlockedDecrement(&g_cDllInit); } } g_slDllInit.ExclusiveUnlock(); } else { g_slDllInit.ShareUnlock(); } _ASSERT(g_fInit); return hr; } //+------------------------------------------------------------ // // Function: DllDeinitialize // // Synopsis: Refcounted deinitialize of exchmem and tracing // // Arguments: NONE // // Returns: NOTHING // // History: // jstamerj 1998/12/16 15:46:32: Created. // //------------------------------------------------------------- VOID DllDeinitialize() { // // We don't need to do the decrement inside a sharelock because we // don't care about blocking threads until the DLL is really // DeInitialzied (whereas HrDllInitialize does care) // if(InterlockedDecrement(&g_cDllInit) == 0) { g_slDllInit.ExclusiveLock(); // // If the refcount is still zero, deinitialize // If the refcount is non-zero, someone initialized before we // got the exclusive lock, so do not deinitialize // if(g_cDllInit == 0) { // // If this assert fires, then DllDeinitialize has been // called before DllInitialize returned (or there is a // DllInit/Deinit mismatch) // _ASSERT(g_fInit == TRUE); // // Termiante exchmem and tracing // if(!TrHeapDestroy()) { TraceFunctEnter("DllDeinitialize"); ErrorTrace((LPARAM) 0, "Unable to Destroy Exchmem heap for Advanced Queuing"); TraceFunctLeave(); } TermAsyncTrace(); g_fInit = FALSE; } else { // // Someone called initialize between the time we // decremented the count and got the exclusive lock. In // this case we don't want to deinitialize // } g_slDllInit.ExclusiveUnlock(); } } #include