/***************************************************************************\ * * File: ResourceManager.cpp * * Description: * This file implements the ResourceManager used to setup and maintain all * Thread, Contexts, and other resources used by and with DirectUser. * * * History: * 4/18/2000: JStall: Created * * Copyright (C) 2000 by Microsoft Corporation. All rights reserved. * \***************************************************************************/ #include "stdafx.h" #include "Services.h" #include "ResourceManager.h" #include "Thread.h" #include "Context.h" #include "OSAL.h" #include "Hook.h" #include // For error handling & advanced features static const GUID guidCreateBuffer = { 0xd2139559, 0x458b, 0x4ba8, { 0x82, 0x28, 0x34, 0xd7, 0x57, 0x3d, 0xa, 0x8 } }; // {D2139559-458B-4ba8-8228-34D7573D0A08} static const GUID guidInitGdiplus = { 0x49f9b12e, 0x846b, 0x4973, { 0xab, 0xfb, 0x7b, 0xe3, 0x4b, 0x52, 0x31, 0xfe } }; // {49F9B12E-846B-4973-ABFB-7BE34B5231FE} /***************************************************************************\ ***************************************************************************** * * class ResourceManager * ***************************************************************************** \***************************************************************************/ #if DBG static BOOL g_fAlreadyShutdown = FALSE; #endif // DBG long ResourceManager::s_fInit = FALSE; HANDLE ResourceManager::s_hthSRT = NULL; DWORD ResourceManager::s_dwSRTID = 0; HANDLE ResourceManager::s_hevReady = NULL; HGADGET ResourceManager::s_hgadMsg = NULL; MSGID ResourceManager::s_idCreateBuffer = 0; MSGID ResourceManager::s_idInitGdiplus = 0; RMData * ResourceManager::s_pData = NULL; CritLock ResourceManager::s_lockContext; CritLock ResourceManager::s_lockComponent; Thread * ResourceManager::s_pthrSRT = NULL; GList ResourceManager::s_lstAppThreads; int ResourceManager::s_cAppThreads = 0; GList ResourceManager::s_lstComponents; BOOL ResourceManager::s_fInitGdiPlus = FALSE; ULONG_PTR ResourceManager::s_gplToken = 0; Gdiplus::GdiplusStartupOutput ResourceManager::s_gpgso; #if DBG_CHECK_CALLBACKS int ResourceManager::s_cTotalAppThreads = 0; BOOL ResourceManager::s_fBadMphInit = FALSE; #endif BEGIN_STRUCT(GMSG_CREATEBUFFER, EventMsg) IN HDC hdc; // DC to be compatible with IN SIZE sizePxl; // Size of bitmap OUT HBITMAP hbmpNew; // Allocated bitmap END_STRUCT(GMSG_CREATEBUFFER) /***************************************************************************\ * * ResourceManager::Create * * Create() is called when DUser.DLL is loaded to initialize low-level * services in DirectUser. * * NOTE: It is very important to keep this function small and to * delay-initialize to help keep starting costs low. * * NOTE: This function is automatically synchronized because it is called * in PROCESS_ATTACH in DllMain(). Therefore, only one thread will ever be * in this function at one time. * \***************************************************************************/ HRESULT ResourceManager::Create() { AssertMsg(!g_fAlreadyShutdown, "Ensure shutdown has not already occurred"); #if USE_DYNAMICTLS g_tlsThread = TlsAlloc(); if (g_tlsThread == (DWORD) -1) { return E_OUTOFMEMORY; } #endif if (InterlockedCompareExchange(&s_fInit, TRUE, FALSE) == TRUE) { return S_OK; } // // Initialize low-level resources (such as the heap). This must be // carefully done since many objects have not been constructed yet. // s_hthSRT = NULL; s_fInit = FALSE; s_hevReady = NULL; HRESULT hr = OSAL::Init(); if (FAILED(hr)) { return hr; } // // Create global services / managers // s_pData = ProcessNew(RMData); if (s_pData == NULL) { return E_OUTOFMEMORY; } return S_OK; } /***************************************************************************\ * * ResourceManager::xwDestroy * * xwDestroy() is called when DUser.DLL is unloaded to perform final clean-up * in DirectUser. * * NOTE: This function is automatically synchronized because it is called * in PROCESS_ATTACH in DllMain(). Therefore, only one thread will ever be * in this function at one time. * \***************************************************************************/ void ResourceManager::xwDestroy() { AssertMsg(!g_fAlreadyShutdown, "Ensure shutdown has not already occurred"); #if DBG g_fAlreadyShutdown = TRUE; #endif // DBG // // Check if there are any remaining Contexts. Unfortunately, we CAN NOT // perform any cleanup work since we are in User mode and are limited by // what we can do while inside the "Loader Lock" in DllMain(). We can not // clean up any objects because these may cause deadlocks, such as freeing // another library. We also must be very cautious about waiting on // anything, since we can easily get into a deadlock. // // This is a serious application error. The application MUST call // ::DeleteHandle() on the Context before the thread exits. // if (s_cAppThreads != 0) { OutputDebugString("ERROR: Not all DirectUser Contexts were destroyed before EndProcess().\n"); PromptInvalid("Not all DirectUser Contexts were destroyed before EndProcess()."); while (!s_lstAppThreads.IsEmpty()) { Thread * pthr = s_lstAppThreads.UnlinkHead(); pthr->MarkOrphaned(); pthr->GetContext()->MarkOrphaned(); } s_cAppThreads = 0; } else { // // If there are no leaked application threads, there should no longer be // any SRT, since it should be cleaned up when the last application // thread is cleaned up. // AssertMsg(s_pthrSRT == NULL, "Destruction should reset s_pthrSRT"); AssertMsg(s_lstAppThreads.IsEmpty(), "Should not have any threads"); } ForceSetContextHeap(NULL); #if USE_DYNAMICTLS Verify(TlsSetValue(g_tlsThread, NULL)); #else t_pContext = NULL; t_pThread = NULL; #endif // // Clean up remaining resources. // NOTE: This can NOT use the Context heaps (via new / delete) because they // have already been destroyed. // ProcessDelete(RMData, s_pData); s_pData = NULL; if (s_hevReady != NULL) { CloseHandle(s_hevReady); s_hevReady = NULL; } // // Because they are global variables, we need to manually unlink all of the // Component Factories so that they don't get deleted. // s_lstComponents.UnlinkAll(); #if USE_DYNAMICTLS Verify(TlsFree(g_tlsThread)); g_tlsThread = (DWORD) -1; // TLS slot no longer valid #endif } /***************************************************************************\ * * ResourceManager::ResetSharedThread * * ResetSharedThread() cleans up SRT data. * \***************************************************************************/ void ResourceManager::ResetSharedThread() { // // Access to the SRT is normally serialized through DirectUser's queues. // In the case where the data is directly being cleaned up, we need to // guarantee that only one thread is accessing this data. This should // always be true since it will either be the SRT properly shutting down // or the main application's thread that is cleaning up dangling Contexts. // AssertMsg(s_cAppThreads == 0, "Must not have any outstanding application threads"); s_dwSRTID = 0; if (s_hgadMsg != NULL) { ::DeleteHandle(s_hgadMsg); s_hgadMsg = NULL; } s_pthrSRT = NULL; // // NOTE: It is important not to call DeleteHandle() on the SRT's Context // here since this function may be called by the application thread which // is already cleaning up the Context. // } /***************************************************************************\ * * ResourceManager::SharedThreadProc * * SharedThreadProc() provides a "Shared Resource Thread" that is processes * requests from other DirectUser threads. The SRT is created in the first * call to InitContextNL(). * * NOTE: The SRT is ONLY created when SEPARATE or MULTIPLE threading models * are used. The SRT is NOT created for SINGLE threading model. * \***************************************************************************/ unsigned __stdcall ResourceManager::SharedThreadProc( IN void * pvArg) { UNREFERENCED_PARAMETER(pvArg); AssertMsg(s_dwSRTID == 0, "SRT should not already be initialized"); s_dwSRTID = GetCurrentThreadId(); Context * pctx; INITGADGET ig; ZeroMemory(&ig, sizeof(ig)); ig.cbSize = sizeof(ig); ig.nThreadMode = IGTM_SEPARATE; ig.nMsgMode = IGMM_ADVANCED; HRESULT hr = InitContextNL(&ig, TRUE /* SRT */, &pctx); if (FAILED(hr)) { return hr; } // // Setup a Gadget to receive custom requests to execute on this thread. // Each of these requests uses a registered message. // if (((s_idCreateBuffer = RegisterGadgetMessage(&guidCreateBuffer)) == 0) || ((s_idInitGdiplus = RegisterGadgetMessage(&guidInitGdiplus)) == 0) || ((s_hgadMsg = CreateGadget(NULL, GC_MESSAGE, SharedEventProc, NULL)) == 0)) { hr = GetLastError(); goto Exit; } // // The SRT is fully initialized and can start processing messages. Signal // the calling thread and start the message loop. // // NOTE: See MSDN docs for PostThreadMessage() that explain why we need the // extra PeekMessage() in the beginning to force User to create a queue for // us. // MSG msg; PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); Verify(SetEvent(s_hevReady)); BOOL fQuit = FALSE; while ((!fQuit) && GetMessageEx(&msg, NULL, 0, 0)) { AssertMsg(IsMultiThreaded(), "Must remain multi-threaded if using SRT"); if (msg.message == WM_QUIT) { fQuit = TRUE; } TranslateMessage(&msg); DispatchMessage(&msg); } // // Uninitialize GDI+ // // If GDI+ has been initialized on any thread, we need to uninitialize it // when the SRT is going away. // if (IsInitGdiPlus()) { (s_gpgso.NotificationUnhook)(s_gplToken); } hr = S_OK; Exit: // // The SRT is going away: // - Clean up remaining SRT data // - Destroy the SRT's Context // ResetSharedThread(); DeleteHandle(pctx->GetHandle()); return hr; } /***************************************************************************\ * * ResourceManager::SharedEventProc * * SharedEventProc() processes LPC requests sent to the SRT. * * NOTE: The SRT is ONLY created when SEPARATE or MULTIPLE threading models * are used. The SRT is NOT created for SINGLE threading model. * \***************************************************************************/ HRESULT ResourceManager::SharedEventProc( IN HGADGET hgadCur, IN void * pvCur, IN EventMsg * pMsg) { UNREFERENCED_PARAMETER(hgadCur); UNREFERENCED_PARAMETER(pvCur); AssertMsg(IsMultiThreaded(), "Must remain multi-threaded if using SRT"); if (pMsg->nMsg == s_idCreateBuffer) { // // Create a new bitmap // GMSG_CREATEBUFFER * pmsgCB = (GMSG_CREATEBUFFER *) pMsg; pmsgCB->hbmpNew = CreateCompatibleBitmap(pmsgCB->hdc, pmsgCB->sizePxl.cx, pmsgCB->sizePxl.cy); #if DBG if (pmsgCB->hbmpNew == NULL) { Trace("CreateCompatibleBitmap failed: LastError = %d\n", GetLastError()); } #endif // DBG return DU_S_COMPLETE; } else if (pMsg->nMsg == s_idInitGdiplus) { // // Initialize GDI+ // (s_gpgso.NotificationHook)(&s_gplToken); return DU_S_COMPLETE; } return DU_S_NOTHANDLED; } /***************************************************************************\ * * ResourceManager::InitContextNL * * InitContextNL() initializes a thread into either a new or existing * DirectUser Context. The Context is valid in the Thread until it is * explicitely destroyed with ::DeleteHandle() or the thread exits. * * NOTE: It is VERY important that the first time this function is called is * NOT in DllMain() because we need to initialize the SRT. DllMain() * serializes access across all threads, so we will deadlock. After the first * Context is successfully created, additional Contexts can be created inside * DllMain(). * * DU_E_GENERIC * E_OUTOFMEMORY * E_NOTIMPL * E_INVALIDARG * DU_E_THREADINGALREADYSET * \***************************************************************************/ HRESULT ResourceManager::InitContextNL( IN INITGADGET * pInit, // Context description IN BOOL fSharedThread, // Context is for the shared thread OUT Context ** ppctxNew) // New context { HRESULT hr = DU_E_GENERIC; *ppctxNew = NULL; #if DBG_CHECK_CALLBACKS BOOL fInitMPH = FALSE; #endif // // Can not initialize inside DllMain() // if (OS()->IsInsideLoaderLock()) { PromptInvalid("Can not initialize DirectUser inside DllMain()"); return E_INVALIDARG; } // // If Context is already initialized, increment the number of times this // THREAD has been initialized. We need to remember each thread // individually. Since we only lock individual threads, we don't need to // worry about synchronization yet. // if (IsInitContext()) { Thread * pthrExist = GetThread(); AssertInstance(pthrExist); // Initialized Context must already initialize Thread pthrExist->Lock(); *ppctxNew = pthrExist->GetContext(); return S_OK; } // // Before initializing the new Context, ensure that the Shared Resource // Thread has been created. We want to create the shared thread Context // before creating this thread's Context so that as soon as we return, // everything is valid. // // If we are initializing the Shared Resource Thread, don't take the lock. // This is because we are already inside the lock in InitContextNL() on // another thread waiting for the SRT to initialize. // if (fSharedThread) { AssertMsg(s_pthrSRT == NULL, "Only should initialize a single SRT"); } else { s_lockContext.Enter(); } #if DBG int DEBUG_cOldAppThreads = s_cAppThreads; #endif if (pInit->nThreadMode != IGTM_NONE) { // // Setup the threading model. By default, we start in multi-threading // model. We can only change to single threaded if no threads are // already initialized. // BOOL fAlreadyInit = (!s_lstAppThreads.IsEmpty()) && (s_pthrSRT == NULL); switch (pInit->nThreadMode) { case IGTM_SINGLE: if (fAlreadyInit) { hr = DU_E_THREADINGALREADYSET; goto RawErrorExit; } else { g_fThreadSafe = FALSE; } break; case IGTM_SEPARATE: case IGTM_MULTIPLE: if (!g_fThreadSafe) { hr = DU_E_THREADINGALREADYSET; goto RawErrorExit; } break; default: AssertMsg(0, "Unknown threading model"); hr = E_INVALIDARG; goto RawErrorExit; } } if (IsMultiThreaded() && (!fSharedThread)) { hr = InitSharedThread(); if (FAILED(hr)) { goto RawErrorExit; } } { DUserHeap * pHeapNew = NULL; Context * pctxShare = NULL; Context * pctxNew = NULL; Context * pctxActual = NULL; Thread * pthrNew = NULL; #if ENABLE_MPH BOOL fDanglingMPH = FALSE; #endif // // If the Context being created is separate, then it can't be shared. // if ((pInit->nThreadMode == IGTM_SEPARATE) && (pInit->hctxShare != NULL)) { PromptInvalid("Can not use IGTM_SEPARATE for shared Contexts"); hr = E_INVALIDARG; goto RawErrorExit; } // // Initialize low-level resources (such as the heap). If a Context is // specified to share resources with, use the existing one. If no Context // is specified, need to create new resources. // // NOTE: If this is running on the main thread, the heap will already have // been created. // BOOL fThreadSafe; switch (pInit->nThreadMode) { case IGTM_SINGLE: case IGTM_SEPARATE: fThreadSafe = FALSE; break; default: fThreadSafe = TRUE; } DUserHeap::EHeap idHeap; #ifdef _DEBUG idHeap = DUserHeap::idCrtDbgHeap; #else // _DEBUG switch (pInit->nPerfMode) { case IGPM_SPEED: #ifdef _X86_ idHeap = DUserHeap::idRockAllHeap; #else idHeap = DUserHeap::idNtHeap; #endif break; case IGPM_BLEND: if (IsRemoteSession()) { idHeap = DUserHeap::idProcessHeap; } else { #ifdef _X86_ idHeap = DUserHeap::idRockAllHeap; #else idHeap = DUserHeap::idNtHeap; #endif } break; case IGPM_SIZE: default: idHeap = DUserHeap::idProcessHeap; break; } #endif // _DEBUG if (pInit->hctxShare != NULL) { BaseObject * pObj = BaseObject::ValidateHandle(pInit->hctxShare); if (pObj != NULL) { // // Note: We need to manually enter the Context here- can not use // a ContextLock object because the thread is not initialized yet. // pctxShare = CastContext(pObj); if (pctxShare == NULL) { hr = E_INVALIDARG; goto ErrorExit; } BOOL fError = FALSE; pctxShare->Enter(); if (pctxShare->GetThreadMode() == IGTM_SEPARATE) { PromptInvalid("Can not share with an IGTM_SEPARATE Context"); hr = E_INVALIDARG; fError = TRUE; } else { pctxShare->Lock(); DUserHeap * pHeapExist = pctxShare->GetHeap(); DUserHeap * pHeapTemp; // Use temp b/c don't destroy if failure VerifyMsgHR(CreateContextHeap(pHeapExist, fThreadSafe, idHeap, &pHeapTemp), "Always should be able to copy the heap"); VerifyMsg(pHeapTemp == pHeapExist, "Ensure heaps match"); } pctxShare->Leave(); if (fError) { Assert(FAILED(hr)); goto ErrorExit; } } } else { if (FAILED(CreateContextHeap(NULL, fThreadSafe, idHeap, &pHeapNew))) { hr = E_OUTOFMEMORY; goto ErrorExit; } } #if ENABLE_MPH // // Setup the WindowManagerHooks. We do this BEFORE setting up the // thread, since the MPH's will always be "uninit" in the Thread's // destructor. However, until the thread is successfully setup, the // MPH's are dangling and need to be cleaned up manually. // if (pInit->nMsgMode == IGMM_STANDARD) { if (!InitMPH()) { hr = DU_E_CANNOTUSESTANDARDMESSAGING; goto ErrorExit; } fDanglingMPH = TRUE; #if DBG_CHECK_CALLBACKS s_fBadMphInit = TRUE; fInitMPH = TRUE; #endif } #endif // // Initialize the Thread // AssertMsg(!IsInitThread(), "Thread should not already be initialized"); hr = Thread::Build(fSharedThread, &pthrNew); if (FAILED(hr)) { goto ErrorExit; } if (fSharedThread) { Assert(s_pthrSRT == NULL); s_pthrSRT = pthrNew; } else { s_lstAppThreads.Add(pthrNew); s_cAppThreads++; #if DBG_CHECK_CALLBACKS s_cTotalAppThreads++; #endif } #if ENABLE_MPH fDanglingMPH = FALSE; #endif // // Initialize the actual Context. // // NOTE: pHeapNew will only be initialized if we are building a new // Context. If we are linking into an existing Context, we do not // create a _new_ Context heap. // if (pctxShare == NULL) { AssertMsg(pHeapNew != NULL, "Must create a new heap for a new Context"); hr = Context::Build(pInit, pHeapNew, &pctxNew); if (FAILED(hr)) { goto ErrorExit; } pctxActual = pctxNew; } else { // // Linking this thread to a shared Context, so just use the existing // Context. We have already Lock()'d the Context earlier. // AssertMsg(pHeapNew == NULL, "Should not create a new heap for existing Context"); pctxShare->AddCurrentThread(); pctxActual = pctxShare; } AssertMsg(fSharedThread || ((s_cAppThreads - DEBUG_cOldAppThreads) == 1), "Should have created a single new app threads on success"); #if DBG_CHECK_CALLBACKS s_fBadMphInit = FALSE; #endif if (!fSharedThread) { s_lockContext.Leave(); // // NOTE: Can no longer goto ErrorExit or RawErrorExit for cleanup // because we have left s_lockContext. // } *ppctxNew = pctxActual; return S_OK; ErrorExit: // // NOTE: Do NOT destroy pctxNew on failure, since it was already // attached to the newly created Thread object. When this Thread is // unlocked (destroyed), it will also destroy the Context. // // If we try and unlock the Context here, it will be unlocked twice. // See Context::Context() for more information. // if (pthrNew != NULL) { xwDoThreadDestroyNL(pthrNew); pthrNew = NULL; } AssertMsg(DEBUG_cOldAppThreads == s_cAppThreads, "Should have same number of app threads on failure"); #if ENABLE_MPH if (fDanglingMPH) { #if DBG_CHECK_CALLBACKS if (UninitMPH()) { s_fBadMphInit = FALSE; } #else // DBG_CHECK_CALLBACKS UninitMPH(); #endif // DBG_CHECK_CALLBACKS } #endif // ENABLE_MPH if (pHeapNew != NULL) { DestroyContextHeap(pHeapNew); pHeapNew = NULL; } } RawErrorExit: #if DBG_CHECK_CALLBACKS if (fInitMPH) { if (s_fBadMphInit) { AlwaysPromptInvalid("Unsuccessfully uninitialized MPH on Context creation failure"); } } #endif // DBG_CHECK_CALLBACKS if (!fSharedThread) { s_lockContext.Leave(); } AssertMsg(FAILED(hr), "ErrorExit requires a failure code"); return hr; } /***************************************************************************\ * * ResourceManager::InitComponentNL * * InitComponentNL() initializes an optional DirectUser component to be shared * across all Contexts. The component is valid until either it is explicitely * uninitialized with UninitComponent() or the process ends. * * NOTE: InitComponentNL() doesn't actually synchronize on a Context, but needs * a context to be initialized so that the threading model is determined. * * DU_E_GENERIC * DU_E_CANNOTLOADGDIPLUS * \***************************************************************************/ HRESULT ResourceManager::InitComponentNL( IN UINT nOptionalComponent) // Optional component to load { // // NOTE: Initializing and unintializaing Components CAN NOT use the // s_lockContext since they may destroy Threads. This will call // xwNotifyThreadDestroyNL() to cleanup the thread's Context and would // create a deadlock. Therefore, we must be very careful when // initializing and uninitializing components because Contexts may be // created and destroyed in the middle of this. // HRESULT hr; AssertMsg(IsInitContext(), "Context must be initialized to determine threading model"); s_lockComponent.Enter(); switch (nOptionalComponent) { case IGC_DXTRANSFORM: hr = s_pData->manDX.Init(); if (FAILED(hr)) { goto Exit; } hr = s_pData->manDX.InitDxTx(); if (FAILED(hr)) { goto Exit; } hr = S_OK; break; case IGC_GDIPLUS: if (s_fInitGdiPlus) { hr = S_OK; // GDI+ is already loaded } else { // // GDI+ has not already been loaded, so safely load and initialize // it. // hr = DU_E_CANNOTLOADGDIPLUS; // Assume failure unless pass all tests Gdiplus::GdiplusStartupInput gpgsi(NULL, TRUE); if (Gdiplus::GdiplusStartup(&s_gplToken, &gpgsi, &s_gpgso) == Gdiplus::Ok) { s_fInitGdiPlus = TRUE; RequestInitGdiplus(); hr = S_OK; } } break; default: { hr = E_NOTIMPL; ComponentFactory * pfac = s_lstComponents.GetHead(); while (pfac != NULL) { hr = pfac->Init(nOptionalComponent); if (hr != E_NOTIMPL) { break; } pfac = pfac->GetNext(); } } } Exit: s_lockComponent.Leave(); return hr; } /***************************************************************************\ * * ResourceManager::UninitComponentNL * * UninitComponentNL() frees up resources associated with a previously * initialized optional component. * \***************************************************************************/ HRESULT ResourceManager::UninitComponentNL( IN UINT nOptionalComponent) // Optional component to unload { // // NOTE: See warning in InitComponent() about locks and re-entrancy issues. // HRESULT hr; s_lockComponent.Enter(); switch (nOptionalComponent) { case IGC_DXTRANSFORM: s_pData->manDX.UninitDxTx(); s_pData->manDX.Uninit(); hr = S_OK; break; case IGC_GDIPLUS: // // GDI+ can not be uninitialized by the application. Since various // DirectUser objects create and cache GDI+ objects, we have to // postpone uninitializing GDI+ until all of the Contexts have been // destroyed. // hr = S_OK; break; default: { hr = E_NOTIMPL; ComponentFactory * pfac = s_lstComponents.GetHead(); while (pfac != NULL) { hr = pfac->Init(nOptionalComponent); if (hr != E_NOTIMPL) { break; } pfac = pfac->GetNext(); } } } s_lockComponent.Leave(); return hr; } /***************************************************************************\ * * ResourceManager::UninitAllComponentsNL * * UninitAllComponentsNL() uninitializes all dynamically initialized * components and other global services. This is called when all application * threads have been destroyed and DirectUser is shutting down. * * NOTE: This may or may not happen inside DllMain(). * \***************************************************************************/ void ResourceManager::UninitAllComponentsNL() { s_lockComponent.Enter(); s_pData->manDX.Uninit(); if (IsInitGdiPlus()) { if (!IsMultiThreaded()) { // // GDI+ has been initialized, but we are running in single // threaded mode, so we need to uninitialize GDI+ here // because there is no SRT. // (s_gpgso.NotificationUnhook)(s_gplToken); } Gdiplus::GdiplusShutdown(s_gplToken); } s_lockComponent.Leave(); } /***************************************************************************\ * * ResourceManager::RegisterComponentFactory * * RegisterComponentFactory() adds a ComponentFactory to the list of * factories queried when a dynamic component needs to be initialized. * \***************************************************************************/ void ResourceManager::RegisterComponentFactory( IN ComponentFactory * pfac) { s_lstComponents.Add(pfac); } /***************************************************************************\ * * ResourceManager::InitSharedThread * * InitSharedThread() ensures that the SRT for the process has been * initialized. If it has not already been initialized, the SRT will be * created and initialized. The SRT is valid until the process shuts down. * * NOTE: It is VERY important that the SRT is NOT initialized while processing * DllMain() because it creates a new thread and blocks until the thread is * ready to process requests. DllMain() serializes access across all threads, * so we will deadlock. * \***************************************************************************/ HRESULT ResourceManager::InitSharedThread() { AssertMsg(IsMultiThreaded(), "Only initialize when multi-threaded"); if (s_hthSRT != NULL) { return S_OK; } // // TODO: Need to LoadLibrary() to keep the SRT from going away underneath // us. We also need to FreeLibrary(), but can not do this inside DllMain(). // Also need to modify all exit paths to properly FreeLibrary() after this. // // Create a thread to handle these requests. Wait until an event has been // signaled that the thread is ready to start receiving events. We need to // do this to ensure that the msgid's have been properly setup. // // This function is already called inside the lock, so we don't need to take // it again. // HRESULT hr; HINSTANCE hinstLoad = NULL; AssertMsg(s_hthSRT == NULL, "Ensure Thread is not already initialized"); if (s_hevReady == NULL) { s_hevReady = CreateEvent(NULL, TRUE, FALSE, NULL); if (s_hevReady == NULL) { hr = E_OUTOFMEMORY; goto Exit; } } AssertMsg(WaitForSingleObject(s_hevReady, 0) == WAIT_TIMEOUT, "Event was not Reset() after used last"); // // Start the Thread. DirectUser uses the CRT, so we use _beginthreadex(). // hinstLoad = LoadLibrary("DUser.dll"); AssertMsg(hinstLoad == g_hDll, "Must load the same DLL"); unsigned thrdaddr; s_hthSRT = (HANDLE) _beginthreadex(NULL, 0, SharedThreadProc, NULL, 0, &thrdaddr); if (s_hthSRT == NULL) { hr = E_OUTOFMEMORY; goto Exit; } HANDLE rgh[2]; rgh[0] = s_hevReady; rgh[1] = s_hthSRT; switch (WaitForMultipleObjects(_countof(rgh), rgh, FALSE, INFINITE)) { case WAIT_OBJECT_0: // // SRT is now properly setup and ready to process requests. // hr = S_OK; break; case WAIT_OBJECT_0 + 1: // // SRT thread was successfully created, but it failed to setup. // { DWORD dwExitCode; Verify(GetExitCodeThread(s_hthSRT, &dwExitCode)); hr = (HRESULT) dwExitCode; // // NOTE: Calling UninitSharedThread() will clean up both the // dangling thread handle and DLL hinstances. // UninitSharedThread(TRUE /* Aborting */); } break; default: AssertMsg(0, "Unknown return code"); hr = E_FAIL; } ResetEvent(s_hevReady); // Clean up the event for the next user / next time // // TODO: May need to change to have a message loop in // MsgWaitForMultipleObjects() so that we can process UI requests while this // thread is being created. It may actually be important if this thread // creates objects that may signal other objects in other threads and could // potentially dead-lock. // hinstLoad = NULL; Exit: // // Need to FreeLibrary() on any errors. // if (hinstLoad != NULL) { FreeLibrary(hinstLoad); } return hr; } /***************************************************************************\ * * ResourceManager::UninitSharedThread * * UninitSharedThread() uninitializes the SRT and is called when all * application threads have been uninitialized. * \***************************************************************************/ void ResourceManager::UninitSharedThread( IN BOOL fAbortInit) // Aborting SRT thread initialization { AssertMsg(IsMultiThreaded(), "Only initialize when multi-threaded"); // // When destroying the SRT, we need to wait until the SRT has properly // cleaned up. Because we are waiting, we need to worry about dead-locks. // Practically, this means that we can not be inside DllMain(), because // the Loader Lock will be a problem when the SRT tries to unload any // dynamically loaded DLL's. // // To ensure against this, we check that the caller doesn't call // DeleteHandle() inside the Loader Lock. We will still allow it (and // dead-lock the app), but we Prompt and notify the developer that their // application is busted and needs to properly call DeleteHandle() before // entering the Loader Lock. // AssertMsg(s_dwSRTID != 0, "Must have valid SRT Thread ID"); if (!fAbortInit) { Verify(PostThreadMessage(s_dwSRTID, WM_QUIT, 0, 0)); WaitForSingleObject(s_hthSRT, INFINITE); } FreeLibrary(g_hDll); CloseHandle(s_hthSRT); s_hthSRT = NULL; } /***************************************************************************\ * * ResourceManager::xwNotifyThreadDestroyNL * * xwNotifyThreadDestroyNL() is called by DllMain when a thread has been * destroyed. This provides DirectUser an opportunity to clean up resources * associated with the Thread before all Thread's are cleaned up at the end * of the application. * \***************************************************************************/ void ResourceManager::xwNotifyThreadDestroyNL() { Thread * pthrDestroy = RawGetThread(); if (pthrDestroy != NULL) { BOOL fValid = pthrDestroy->Unlock(); if (!fValid) { // // The Thread has finally been unlocked, so we can start its // destruction // BOOL fSRT = pthrDestroy->IsSRT(); if (!fSRT) { s_lockContext.Enter(); } xwDoThreadDestroyNL(pthrDestroy); if (!fSRT) { s_lockContext.Leave(); } } } } /***************************************************************************\ * * ResourceManager::xwDoThreadDestroyNL * * xwDoThreadDestroyNL() provides the heart of thread destruction. This may * be called in several situations: * - When DirectUser notices a thread has been destroyed in DllMain() * - When the application calls DeleteHandle() on a Context. * - When DirectUser is destroying the ResourceManager in DllMain() and is * destroying any outstanding threads. * \***************************************************************************/ void ResourceManager::xwDoThreadDestroyNL( IN Thread * pthrDestroy) // Thread to destroy { // // Can not uninitialize inside DllMain(), but we can't just return. // Instead, the process is very likely to dead-lock. // if (OS()->IsInsideLoaderLock()) { PromptInvalid("Can not uninitialize DirectUser inside DllMain()"); } BOOL fSRT = pthrDestroy->IsSRT(); // // Destroy the Thread object and reset the t_pThread pointer. As each is // extracted, set the current Thread and Context pointers so that the // cleanup code can reference these. When finished, set t_pThread and // t_pContext to NULL since there is no Thread or Context for this thread. // #if USE_DYNAMICTLS Verify(TlsSetValue(g_tlsThread, pthrDestroy)); Context * pContext = pthrDestroy->GetContext(); if (pContext != NULL) { ForceSetContextHeap(pContext->GetHeap()); } #else t_pThread = pthrDestroy; t_pContext = pthrDestroy->GetContext(); if (t_pContext != NULL) { ForceSetContextHeap(t_pContext->GetHeap()); } #endif if (fSRT) { ResetSharedThread(); } else { s_lstAppThreads.Unlink(pthrDestroy); } ProcessDelete(Thread, pthrDestroy); // This is the "xw" function ForceSetContextHeap(NULL); #if USE_DYNAMICTLS pContext = NULL; Verify(TlsSetValue(g_tlsThread, NULL)); #else t_pContext = NULL; t_pThread = NULL; #endif // // Clean-up when there are no longer any application threads: // - Destroy the SRT // - Destroy global services / managers // if (!fSRT) { if (--s_cAppThreads == 0) { if (IsMultiThreaded()) { UninitSharedThread(FALSE /* Proper shutdown */); } else { AssertMsg(s_hthSRT == NULL, "Should never have initialized SRT for single-threaded"); } UninitAllComponentsNL(); } } } //------------------------------------------------------------------------------ HBITMAP ResourceManager::RequestCreateCompatibleBitmap( IN HDC hdc, IN int cxPxl, IN int cyPxl) { if (IsMultiThreaded()) { GMSG_CREATEBUFFER msg; msg.cbSize = sizeof(msg); msg.nMsg = s_idCreateBuffer; msg.hgadMsg = s_hgadMsg; msg.hdc = hdc; msg.sizePxl.cx = cxPxl; msg.sizePxl.cy = cyPxl; msg.hbmpNew = NULL; if (DUserSendEvent(&msg, 0) == DU_S_COMPLETE) { return msg.hbmpNew; } OutputDebugString("ERROR: RequestCreateCompatibleBitmap failed\n"); return NULL; } else { return CreateCompatibleBitmap(hdc, cxPxl, cyPxl); } } //------------------------------------------------------------------------------ void ResourceManager::RequestInitGdiplus() { AssertMsg(s_fInitGdiPlus, "Only should call when GDI+ is just initialized"); if (IsMultiThreaded()) { EventMsg msg; msg.cbSize = sizeof(msg); msg.nMsg = s_idInitGdiplus; msg.hgadMsg = s_hgadMsg; if (DUserSendEvent(&msg, 0) != DU_S_COMPLETE) { OutputDebugString("ERROR: RequestInitGdiplus failed\n"); } } else { (s_gpgso.NotificationHook)(&s_gplToken); } }