Windows2003-3790/windows/advcore/duser/engine/services/resourcemanager.cpp
2020-09-30 16:53:55 +02:00

1385 lines
40 KiB
C++

/***************************************************************************\
*
* 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 <Delayimp.h> // 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<Thread>
ResourceManager::s_lstAppThreads;
int ResourceManager::s_cAppThreads = 0;
GList<ComponentFactory>
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().
*
* <error> DU_E_GENERIC</>
* <error> E_OUTOFMEMORY</>
* <error> E_NOTIMPL</>
* <error> E_INVALIDARG</>
* <error> 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.
*
* <error> DU_E_GENERIC</>
* <error> 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);
}
}