windows-nt/Source/XPSP1/NT/windows/appcompat/shims/layer/emulatedirectdrawsync.cpp
2020-09-26 16:20:57 +08:00

383 lines
9.1 KiB
C++

/*++
Copyright (c) 2000 Microsoft Corporation
Module Name:
EmulateDirectDrawSync.cpp
Abstract:
DirectDraw uses per-thread exclusive mode arbitration on NT. On Win9x this
is done per process. What this means is that if an app releases exclusive
mode from a different thread than that which acquired it, it will be in a
permanently bad state.
This shim ensures that the mutex is obtained and released on the same
thread. During DLL_PROCESS_ATTACH, a new thread is started: this thread
manages the acquisition and release of the mutex.
Note we can't get the mutex by catching CreateMutex because it's called
from the dllmain of ddraw.dll: so it wouldn't work on win2k.
Notes:
This is a general purpose shim.
History:
09/11/2000 prashkud Created
10/28/2000 linstev Rewrote to work on win2k
02/23/2001 linstev Modified to handle cases where DirectDraw was used
inside DllMains
--*/
#include "precomp.h"
IMPLEMENT_SHIM_BEGIN(EmulateDirectDrawSync)
#include "ShimHookMacro.h"
APIHOOK_ENUM_BEGIN
APIHOOK_ENUM_ENTRY(WaitForSingleObject)
APIHOOK_ENUM_ENTRY(ReleaseMutex)
APIHOOK_ENUM_ENTRY(CloseHandle)
APIHOOK_ENUM_END
// Enum used to tell our thread what to do
enum {sNone, sWaitForSingleObject, sReleaseMutex};
// Events we use to signal our thread to do work and wait until its done
HANDLE g_hWaitEvent;
HANDLE g_hDoneEvent;
HANDLE g_hThread = NULL;
//
// Parameters that are passed between the caller thread and our thread
// Access is synchronized with a critical section
//
CRITICAL_SECTION g_csSync;
DWORD g_dwWait;
DWORD g_dwWaitRetValue;
DWORD g_dwTime;
BOOL g_bRetValue;
// Store the DirectDraw mutex handle
HANDLE g_hDDMutex = 0;
// Thread tracking data so we can identify degenerate cases
DWORD g_dwMutexOwnerThreadId = 0;
// Find the DirectDraw mutex
DWORD g_dwFindMutexThread = 0;
/*++
Unfortunately we don't get in early enough on Win2k to get the mutex from the
ddraw call to CreateMutex, so we have to make use of a special hack that knows
about the ddraw internals.
Ddraw has an export called GetOLEThunkData. The name is chosen to prevent
people from calling it. It is designed to be used by the test harness. One of
the things it can do, is release the exclusive mode mutex. This is the hack
we're exploiting so we can determine the mutex handle.
--*/
BOOL
FindMutex()
{
typedef VOID (WINAPI *_pfn_GetOLEThunkData)(ULONG_PTR dwOrdinal);
HMODULE hMod;
_pfn_GetOLEThunkData pfnGetOLEThunkData;
hMod = GetModuleHandleA("ddraw");
if (!hMod) {
DPFN( eDbgLevelError, "[FindMutex] DirectDraw not loaded");
return FALSE;
}
pfnGetOLEThunkData = (_pfn_GetOLEThunkData) GetProcAddress(hMod, "GetOLEThunkData");
if (!pfnGetOLEThunkData) {
DPFN( eDbgLevelError, "[FindMutex] Failed to get GetOLEThunkData API");
return FALSE;
}
//
// Now we plan to go and find the mutex by getting Ddraw to call
// ReleaseMutex.
//
EnterCriticalSection(&g_csSync);
//
// Set the mutex to the current thread so it can be picked up in the
// ReleaseMutex hook
//
g_dwFindMutexThread = GetCurrentThreadId();
//
// Call to the hard-coded (in ddraw) ReleaseMutex hack which releases the
// mutex
//
pfnGetOLEThunkData(6);
g_dwFindMutexThread = 0;
LeaveCriticalSection(&g_csSync);
return (g_hDDMutex != 0);
}
/*++
Hook WaitForSingleObject to determine when DirectDraw is testing or acquiring
the mutex. If we haven't got the mutex yet, we attempt to find it using our
hack.
--*/
DWORD
APIHOOK(WaitForSingleObject)(
HANDLE hHandle,
DWORD dwMilliSeconds
)
{
if (g_hThread) {
//
// Hack to find the DirectDraw mutex
//
if (!g_hDDMutex) {
FindMutex();
}
if (g_hDDMutex && (hHandle == g_hDDMutex)) {
//
// Use our thread to acquire the mutex. We synchronize since we're
// accessing globals to communicate with our thread.
//
DWORD dwRet;
EnterCriticalSection(&g_csSync);
// Set globals to communicate with our thread
g_dwTime = dwMilliSeconds;
g_dwWait = sWaitForSingleObject;
ResetEvent(g_hDoneEvent);
// Signal our thread to obtain the mutex
SetEvent(g_hWaitEvent);
// Wait until the state of the mutex has been determined
WaitForSingleObject(g_hDoneEvent, INFINITE);
// Code to detect the degenerate
if (g_dwWaitRetValue == WAIT_OBJECT_0) {
g_dwMutexOwnerThreadId = GetCurrentThreadId();
}
dwRet = g_dwWaitRetValue;
LeaveCriticalSection(&g_csSync);
return dwRet;
}
}
return ORIGINAL_API(WaitForSingleObject)(hHandle, dwMilliSeconds);
}
/*++
Hook ReleaseMutex and release the mutex on our thread.
--*/
BOOL
APIHOOK(ReleaseMutex)(
HANDLE hMutex
)
{
if (g_hThread && (g_dwFindMutexThread == GetCurrentThreadId())) {
//
// We're using our hack to find the DirectDraw mutex
//
DPFN( eDbgLevelInfo, "DDraw exclusive mode mutex found");
g_hDDMutex = hMutex;
// Don't release it, since we never acquired it
return TRUE;
}
//
// First try to release it on the current thread. This will only succeed if
// it was obtained on this thread.
//
BOOL bRet = ORIGINAL_API(ReleaseMutex)(hMutex);
if (!bRet && g_hThread && g_hDDMutex && (hMutex == g_hDDMutex)) {
//
// Use our thread to release the mutex. We synchronize since we're
// accessing globals to communicate with our thread.
//
EnterCriticalSection(&g_csSync);
// Set globals to communicate with our thread
g_dwWait = sReleaseMutex;
ResetEvent(g_hDoneEvent);
// Wait until our thread returns
SetEvent(g_hWaitEvent);
// Signal our thread to release the mutex
WaitForSingleObject(g_hDoneEvent, INFINITE);
// Detect degenerate case
if (GetCurrentThreadId() != g_dwMutexOwnerThreadId) {
LOGN( eDbgLevelError, "[ReleaseMutex] DirectDraw synchronization error - correcting");
}
if (g_bRetValue) {
g_dwMutexOwnerThreadId = 0;
}
bRet = g_bRetValue;
LeaveCriticalSection(&g_csSync);
}
return bRet;
}
/*++
Clear our handle in case the app frees ddraw and reloads it.
--*/
BOOL
APIHOOK(CloseHandle)(HANDLE hObject)
{
if (g_hThread && (hObject == g_hDDMutex))
{
DPFN( eDbgLevelInfo, "DDraw exclusive mode mutex closed");
g_hDDMutex = 0;
}
return ORIGINAL_API(CloseHandle)(hObject);
}
/*++
Thread used to do all the mutex operations so we can guarantee that the thread
that acquired the mutex is the same one that releases it.
--*/
VOID
WINAPI
ThreadSyncMutex(
LPVOID lpParameter
)
{
while (1) {
// Wait until we need to acquire or release the mutex
WaitForSingleObject(g_hWaitEvent, INFINITE);
if (g_dwWait == sWaitForSingleObject) {
// WaitForSingleobject() has been called on the Mutex object
g_dwWaitRetValue = ORIGINAL_API(WaitForSingleObject)(
g_hDDMutex, g_dwTime);
}
else if (g_dwWait == sReleaseMutex) {
// ReleaseMutex has been called
g_bRetValue = ORIGINAL_API(ReleaseMutex)(g_hDDMutex);
}
g_dwWait = sNone;
ResetEvent(g_hWaitEvent);
SetEvent(g_hDoneEvent);
}
}
/*++
Register hooked functions
--*/
BOOL
NOTIFY_FUNCTION(
DWORD fdwReason
)
{
if (fdwReason == SHIM_STATIC_DLLS_INITIALIZED) {
//
// We need the critical section all the time
//
InitializeCriticalSection(&g_csSync);
//
// Create Events that will be used for the thread synchronization, i.e
// to synchronize this thread and the one we will be creating ahead. We
// don't clean these up by design. We have to do this stuff here, rather
// than in the process attach, since OpenGL apps and others do DirectX
// stuff in their dllmains.
//
g_hWaitEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!g_hWaitEvent) {
DPFN( eDbgLevelInfo, "Failed to create Event 1");
return FALSE;
}
g_hDoneEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!g_hDoneEvent) {
DPFN( eDbgLevelInfo, "Failed to create Event 2");
return FALSE;
}
// Create our thread
g_hThread = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE) ThreadSyncMutex, NULL, 0,
NULL);
if (!g_hThread) {
DPFN( eDbgLevelInfo, "Failed to create Thread");
return FALSE;
}
}
return TRUE;
}
HOOK_BEGIN
CALL_NOTIFY_FUNCTION
APIHOOK_ENTRY(KERNEL32.DLL, WaitForSingleObject)
APIHOOK_ENTRY(KERNEL32.DLL, ReleaseMutex)
APIHOOK_ENTRY(KERNEL32.DLL, CloseHandle)
HOOK_END
IMPLEMENT_SHIM_END