1385 lines
40 KiB
C++
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);
|
|
}
|
|
}
|