707 lines
18 KiB
C
707 lines
18 KiB
C
/*****************************************************************************
|
|
*
|
|
* DIExcl.c
|
|
*
|
|
* Copyright (c) 1997 Microsoft Corporation. All Rights Reserved.
|
|
*
|
|
* Abstract:
|
|
*
|
|
* Management and negotiation of exclusive access.
|
|
*
|
|
* Contents:
|
|
*
|
|
* Excl_Acquire
|
|
* Excl_Unacquire
|
|
*
|
|
*****************************************************************************/
|
|
|
|
#include "dinputpr.h"
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* The sqiffle for this file.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
#define sqfl sqflExcl
|
|
|
|
#pragma BEGIN_CONST_DATA
|
|
|
|
#ifndef WINNT
|
|
TCHAR c_tszVxd[] = TEXT("\\\\.\\DINPUT.VXD");
|
|
#endif
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @struct SHAREDOBJECT |
|
|
*
|
|
* Each object that can be taken exclusively receives one of
|
|
* these structures. This structure is shared across processes,
|
|
* so you must protect access with the global DLL mutex.
|
|
*
|
|
* You would think that we could just use a named semaphore.
|
|
* Well, that won't work because if the app crashes, nobody
|
|
* will release the semaphore token and the device will be
|
|
* unavailable forever.
|
|
*
|
|
* And we can't use a named mutex because mutexes are tracked
|
|
* on a per-thread basis, but device acquisition is maintained
|
|
* on a per-process basis.
|
|
*
|
|
* So instead we have to roll our own "process-level mutex".
|
|
*
|
|
* To conserve memory, we dump all our tiny <t SHAREDOBJECT>
|
|
* structures into a single page. This means that we cannot
|
|
* support more than about 4000 / sizeof(SHAREDOBJECT) =
|
|
* 140 devices simultaneously acquired exclusively.
|
|
*
|
|
* Since USB maxes out at 64 devices, we've got plenty of room.
|
|
*
|
|
* WARNING! This structure may not change between DEBUG and
|
|
* RETAIL. Otherwise, you have problems if one DirectInput
|
|
* app is using DEBUG and another is using RETAIL.
|
|
*
|
|
* @field GUID | guid |
|
|
*
|
|
* The identifier for the device that is acquired exclusively.
|
|
*
|
|
* @field HWND | hwndOwner |
|
|
*
|
|
* The window handle associated with the device that has
|
|
* obtained exclusive access.
|
|
*
|
|
* @field DWORD | pidOwner |
|
|
*
|
|
* The process ID of the owner window. This is used as a
|
|
* cross-check against <f hwndOwner> in case the application
|
|
* which is the owner suddenly crashes.
|
|
*
|
|
* @field DWORD | discl |
|
|
*
|
|
* Cooperative level with which the device was acquired.
|
|
* We care about foreground-ness so that
|
|
* we can steal acquisition from a window that
|
|
* has stopped responding.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
typedef struct SHAREDOBJECT
|
|
{
|
|
GUID guid;
|
|
HWND hwndOwner;
|
|
DWORD pidOwner;
|
|
DWORD discl;
|
|
} SHAREDOBJECT, *PSHAREDOBJECT;
|
|
|
|
typedef const SHAREDOBJECT *PCSHAREDOBJECT;
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @define csoMax | (cbShared - cbX(SHAREDOBJECTHEADER)) / cbX(SHAREDOBJECT) |
|
|
*
|
|
* The maximum number of simultaneously acquired devices.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
#define cbShared 4096
|
|
#define csoMax ((cbShared - cbX(SHAREDOBJECTHEADER)) / cbX(SHAREDOBJECT))
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @struct SHAREDOBJECTPAGE |
|
|
*
|
|
* A header followed by an array of shared objects.
|
|
*
|
|
* The header must be first. <c g_soh> relies on it.
|
|
*
|
|
* @field SHAREDOBJECTHEADER | soh |
|
|
*
|
|
* The header.
|
|
*
|
|
* @field SHAREDOBJECT | rgso[csoMax] |
|
|
*
|
|
* Array of shared object structures.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
typedef struct SHAREDOBJECTPAGE
|
|
{
|
|
SHAREDOBJECTHEADER soh;
|
|
SHAREDOBJECT rgso[csoMax];
|
|
} SHAREDOBJECTPAGE, *PSHAREDOBJECTPAGE;
|
|
|
|
void INLINE
|
|
CheckSharedObjectPageSize(void)
|
|
{
|
|
CAssertF(cbX(SHAREDOBJECTPAGE) <= cbShared);
|
|
CAssertF(cbX(SHAREDOBJECTPAGE) + cbX(SHAREDOBJECT) > cbShared);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func PSHAREDOBJECT | Excl_FindGuid |
|
|
*
|
|
* Locate a GUID in the shared object array.
|
|
*
|
|
* The shared global mutex must already be taken.
|
|
*
|
|
* @parm PCGUID | pguid |
|
|
*
|
|
* The GUID to locate.
|
|
*
|
|
* @returns
|
|
*
|
|
* A pointer to the entry, or 0 if not found.
|
|
*
|
|
*
|
|
*****************************************************************************/
|
|
|
|
PSHAREDOBJECT INTERNAL
|
|
Excl_FindGuid(PCGUID pguid)
|
|
{
|
|
PSHAREDOBJECTPAGE psop;
|
|
PSHAREDOBJECT pso, psoMax;
|
|
DWORD Data1;
|
|
EnterProcI(Excl_FindGuid, (_ "G", pguid));
|
|
|
|
psop = g_psop;
|
|
Data1 = pguid->Data1;
|
|
|
|
AssertF(g_psop);
|
|
for(pso = &psop->rgso[0], psoMax = &psop->rgso[psop->soh.cso];
|
|
pso < psoMax; pso++)
|
|
{
|
|
if(pso->guid.Data1 == Data1 && IsEqualGUID(pguid, &pso->guid))
|
|
{
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
pso = 0;
|
|
|
|
done:;
|
|
ExitProcX((UINT_PTR)pso);
|
|
return pso;
|
|
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT | Excl_CanStealPso |
|
|
*
|
|
* Determine whether the <t SHAREDOBJECT> is self-consistent
|
|
* and represents an instance which validly holds the
|
|
* exclusive acquisition. If so, then it cannot be stolen.
|
|
* Else, it is dead and can be stolen.
|
|
*
|
|
* @parm PCSHAREDOBJECT | pso |
|
|
*
|
|
* The <t SHAREDOBJECT> structure to validate.
|
|
*
|
|
* @returns
|
|
*
|
|
* <c S_OK> if acquisition can be stolen, or
|
|
* <c DIERR_OTHERAPPHASPRIO> if acquisition is validly
|
|
* held by another instance.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
STDMETHODIMP
|
|
Excl_CanStealPso(PCSHAREDOBJECT pso)
|
|
{
|
|
HRESULT hres = S_OK;
|
|
|
|
/*
|
|
* The window handle should be valid and still refer to the pid.
|
|
*/
|
|
if(GetWindowPid(pso->hwndOwner) == pso->pidOwner)
|
|
{
|
|
|
|
if( pso->discl & DISCL_FOREGROUND )
|
|
{
|
|
if( GetForegroundWindow() != pso->hwndOwner)
|
|
{
|
|
// 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers.
|
|
RPF("Acquire: can't steal Pso because it belongs to another app. (current hwnd=0x%p)",
|
|
pso->hwndOwner);
|
|
hres = DIERR_OTHERAPPHASPRIO;
|
|
} else
|
|
{
|
|
// 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers.
|
|
RPF("Acquire: Current owner hwnd=0x%p has priority; "
|
|
"stealing", pso->hwndOwner);
|
|
hres = S_OK;
|
|
}
|
|
}
|
|
} else
|
|
{
|
|
/*
|
|
* App died. Can steal.
|
|
*/
|
|
// 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers.
|
|
RPF("Acquire: Previous owner pid=0x%p mysteriously died; "
|
|
"stealing", pso->pidOwner);
|
|
hres = S_OK;
|
|
}
|
|
|
|
return hres;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT | Excl_Acquire |
|
|
*
|
|
* Attempt to acquire the device exclusively.
|
|
*
|
|
* @parm PCGUID | pguid |
|
|
*
|
|
* The GUID to acquire.
|
|
*
|
|
* @parm HWND | hwnd |
|
|
*
|
|
* Window handle with which to associate the device.
|
|
*
|
|
* @parm DWORD | discl |
|
|
*
|
|
* Flags describing cooperative level.
|
|
* We are interested only in devices acquired exclusively.
|
|
*
|
|
* @returns
|
|
*
|
|
* S_OK on success, or
|
|
*
|
|
* DIERR_OTHERAPPHASPRIO
|
|
* hresLe(ERROR_INVALID_WINDOW_HANDLE)
|
|
*
|
|
*
|
|
*****************************************************************************/
|
|
|
|
STDMETHODIMP
|
|
Excl_Acquire(PCGUID pguid, HWND hwnd, DWORD discl)
|
|
{
|
|
HRESULT hres;
|
|
|
|
AssertF(g_psop);
|
|
if(discl & DISCL_EXCLUSIVE)
|
|
{
|
|
|
|
/*
|
|
* Window must be owned by this process.
|
|
*/
|
|
if(GetWindowPid(hwnd) == GetCurrentProcessId())
|
|
{
|
|
|
|
PSHAREDOBJECT pso;
|
|
|
|
WaitForSingleObject(g_hmtxGlobal, INFINITE);
|
|
|
|
pso = Excl_FindGuid(pguid);
|
|
|
|
/*
|
|
* If we found a match, then it might be a sharing violation.
|
|
*/
|
|
if(pso)
|
|
{
|
|
hres = Excl_CanStealPso(pso);
|
|
} else
|
|
{
|
|
/*
|
|
* Allocate a slot for it.
|
|
*/
|
|
if(g_psop->soh.cso < csoMax)
|
|
{
|
|
pso = &g_psop->rgso[g_psop->soh.cso++];
|
|
pso->guid = *pguid;
|
|
hres = S_OK;
|
|
} else
|
|
{
|
|
//ISSUE-2001/03/29-timgill hard limit on number of exclusive devices
|
|
//Can be annoying
|
|
RPF("Too many devices acquired exclusively");
|
|
hres = E_FAIL;
|
|
}
|
|
}
|
|
|
|
if(SUCCEEDED(hres))
|
|
{
|
|
|
|
pso->hwndOwner = hwnd;
|
|
pso->pidOwner = GetCurrentProcessId();
|
|
pso->discl = discl;
|
|
|
|
hres = S_OK;
|
|
}
|
|
|
|
ReleaseMutex(g_hmtxGlobal);
|
|
} else
|
|
{
|
|
hres = hresLe(ERROR_INVALID_WINDOW_HANDLE);
|
|
}
|
|
} else
|
|
{
|
|
hres = S_OK;
|
|
}
|
|
|
|
return hres;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func void | Excl_Unacquire |
|
|
*
|
|
* Undo the effects of an acquire.
|
|
*
|
|
* @parm PCGUID | pguid |
|
|
*
|
|
* The GUID to acquire.
|
|
*
|
|
* @parm HWND | hwnd |
|
|
*
|
|
* Window handle with which to associate the device.
|
|
*
|
|
* @parm DWORD | discl |
|
|
*
|
|
* Flags describing cooperative level.
|
|
* We are interested only in devices acquired exclusively.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
void EXTERNAL
|
|
Excl_Unacquire(PCGUID pguid, HWND hwnd, DWORD discl)
|
|
{
|
|
|
|
AssertF(g_psop);
|
|
if(discl & DISCL_EXCLUSIVE)
|
|
{
|
|
|
|
PSHAREDOBJECT pso;
|
|
|
|
WaitForSingleObject(g_hmtxGlobal, INFINITE);
|
|
|
|
pso = Excl_FindGuid(pguid);
|
|
|
|
/*
|
|
* Make sure it's really ours.
|
|
*/
|
|
if(pso && pso->hwndOwner == hwnd &&
|
|
pso->pidOwner == GetCurrentProcessId())
|
|
{
|
|
|
|
/*
|
|
* Delete the entry and close up the gap.
|
|
*/
|
|
|
|
*pso = g_psop->rgso[--g_psop->soh.cso];
|
|
|
|
}
|
|
|
|
ReleaseMutex(g_hmtxGlobal);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT | Excl_Init |
|
|
*
|
|
* Initialize the exclusive device manager.
|
|
*
|
|
* @returns
|
|
*
|
|
* <c S_OK> if all is well.
|
|
*
|
|
* <c E_FAIL> if something is horribly wrong.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
STDMETHODIMP
|
|
Excl_Init(void)
|
|
{
|
|
HRESULT hres;
|
|
TCHAR tszName[ctchNameGuid];
|
|
|
|
DllEnterCrit();
|
|
|
|
|
|
/*
|
|
* Create the global mutex used to gate access to shared memory.
|
|
*/
|
|
|
|
if(g_hmtxGlobal == 0)
|
|
{
|
|
|
|
NameFromGUID(tszName, &IID_IDirectInputW);
|
|
g_hmtxGlobal = CreateMutex(0, TRUE, tszName);
|
|
|
|
if(g_hmtxGlobal)
|
|
{
|
|
/*
|
|
* If we need to do smth only once, we can do:
|
|
* if ( GetLastError() != ERROR_ALREADY_EXISTS )
|
|
* {
|
|
* do our stuff
|
|
* }
|
|
*/
|
|
|
|
g_flEmulation = RegQueryDIDword(NULL, REGSTR_VAL_EMULATION, 0);
|
|
|
|
#ifndef WINNT
|
|
/*
|
|
* We have to open the VxD while we own the global mutex in order
|
|
* to avoid a race condition that occurs when two processes try
|
|
* to open a VxD at the same time. See DInput VxD for details.
|
|
*/
|
|
if (_OpenVxDHandle)
|
|
{
|
|
/*
|
|
* CreateFile on a \\.\ name does not check the dwCreationDisposition
|
|
* parameter but BoundsChecker does so use a valid value.
|
|
*/
|
|
g_hVxD = CreateFile(c_tszVxd, 0, 0, 0, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, 0);
|
|
|
|
if( g_hVxD != INVALID_HANDLE_VALUE )
|
|
{
|
|
LONG lGranularity;
|
|
|
|
/*
|
|
* If we can't get the sequence number (weird), then set it
|
|
* back to NULL so it will point at the shared memory
|
|
* block just like on NT.
|
|
*/
|
|
if (FAILED(IoctlHw(IOCTL_GETSEQUENCEPTR, 0, 0,
|
|
&g_pdwSequence, cbX(g_pdwSequence))))
|
|
{
|
|
g_pdwSequence = 0;
|
|
}
|
|
|
|
if (SUCCEEDED(IoctlHw(IOCTL_MOUSE_GETWHEEL, 0, 0,
|
|
&lGranularity, cbX(lGranularity))))
|
|
{
|
|
g_lWheelGranularity = lGranularity;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RPF( "ERROR: Cannot load %s", &c_tszVxd[4] );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* We defer ExtDll work until now, because it is not safe to
|
|
* call LoadLibrary during PROCESS_ATTACH.
|
|
*
|
|
* We also steal g_hmtxGlobal to protect us from doing it twice.
|
|
*/
|
|
ExtDll_Init();
|
|
|
|
|
|
ReleaseMutex(g_hmtxGlobal);
|
|
|
|
}
|
|
else
|
|
{
|
|
RPF("Cannot create shared semaphore %s", tszName);
|
|
hres = E_FAIL;
|
|
goto fail;
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* Create the shared memory.
|
|
*
|
|
* Warning! The file mapping handle must be kept alive
|
|
* so its name stays alive. NT destroys the file mapping
|
|
* object when you close the handle; as a result, the
|
|
* name goes away with it and another instance of
|
|
* DirectInput fails to find it.
|
|
*/
|
|
|
|
if(g_psop == 0)
|
|
{
|
|
|
|
NameFromGUID(tszName, &IID_IDirectInputDeviceW);
|
|
|
|
g_hfm = CreateFileMapping(INVALID_HANDLE_VALUE, 0,
|
|
PAGE_READWRITE, 0,
|
|
cbShared, tszName);
|
|
|
|
if(g_hfm)
|
|
{
|
|
g_psop = MapViewOfFile(g_hfm, FILE_MAP_WRITE | FILE_MAP_READ,
|
|
0, 0, 0);
|
|
if(g_psop)
|
|
{
|
|
|
|
} else
|
|
{
|
|
RPF("Cannot map shared memory block %s", tszName);
|
|
hres = E_FAIL;
|
|
goto fail;
|
|
}
|
|
|
|
} else
|
|
{
|
|
RPF("Cannot create shared memory block %s", tszName);
|
|
hres = E_FAIL;
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Create the global mutex used to gate access to joystick info.
|
|
*/
|
|
|
|
if(g_hmtxJoy == 0)
|
|
{
|
|
NameFromGUID(tszName, &IID_IDirectInputDevice2A);
|
|
g_hmtxJoy = CreateMutex(0, 0, tszName);
|
|
|
|
if(g_hmtxJoy)
|
|
{
|
|
|
|
} else
|
|
{
|
|
RPF("Cannot create shared semaphore %s", tszName);
|
|
hres = E_FAIL;
|
|
goto fail;
|
|
}
|
|
|
|
|
|
/*
|
|
* We shall steal the joystick Mutex to build the Bus list
|
|
* for the first time.
|
|
* It is very unlikely that the list will change. ( PCMCIA cards ! )
|
|
* And when it does change we can expect the joyConfig interface will
|
|
* be pinged.
|
|
*/
|
|
DIBus_BuildList(FALSE);
|
|
}
|
|
|
|
|
|
/*
|
|
* If we don't have a global sequence number from the driver,
|
|
* then use the one in the shared memory block instead.
|
|
*/
|
|
if(g_pdwSequence == 0)
|
|
{
|
|
g_pdwSequence = &g_psoh->dwSequence;
|
|
}
|
|
|
|
hres = S_OK;
|
|
|
|
fail:;
|
|
DllLeaveCrit();
|
|
|
|
return hres;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func LONG | Excl_UniqueGuidInteger |
|
|
*
|
|
* Generate a unique number used by DICreateGuid to make sure
|
|
* that we don't generate two pseudoGUIDs with the same value.
|
|
*
|
|
* @returns
|
|
*
|
|
* An integer that has not been returned by this function
|
|
* yet.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
LONG EXTERNAL
|
|
Excl_UniqueGuidInteger(void)
|
|
{
|
|
LONG lRc;
|
|
|
|
AssertF(g_hmtxGlobal);
|
|
|
|
WaitForSingleObject(g_hmtxGlobal, INFINITE);
|
|
|
|
AssertF(g_psop);
|
|
lRc = ++g_psop->soh.cguid;
|
|
|
|
ReleaseMutex(g_hmtxGlobal);
|
|
|
|
return lRc;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func DWORD | Excl_GetConfigChangedTime |
|
|
*
|
|
* Retrieves tmConfigChanged in g_psop->soh.
|
|
*
|
|
* @returns
|
|
*
|
|
* tmConfigChanged
|
|
*****************************************************************************/
|
|
|
|
DWORD EXTERNAL
|
|
Excl_GetConfigChangedTime()
|
|
{
|
|
DWORD dwRc;
|
|
|
|
AssertF(g_hmtxGlobal);
|
|
|
|
WaitForSingleObject(g_hmtxGlobal, INFINITE);
|
|
|
|
AssertF(g_psop);
|
|
dwRc = g_psop->soh.tmConfigChanged;
|
|
|
|
ReleaseMutex(g_hmtxGlobal);
|
|
|
|
return dwRc;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func void | Excl_SetConfigChangedTime |
|
|
*
|
|
* Sets tmConfigChanged in g_psop->soh.
|
|
*
|
|
* @returns
|
|
*
|
|
* void
|
|
*****************************************************************************/
|
|
|
|
void EXTERNAL
|
|
Excl_SetConfigChangedTime(DWORD tm)
|
|
{
|
|
AssertF(g_hmtxGlobal);
|
|
|
|
WaitForSingleObject(g_hmtxGlobal, INFINITE);
|
|
|
|
AssertF(g_psop);
|
|
g_psop->soh.tmConfigChanged = tm;
|
|
|
|
ReleaseMutex(g_hmtxGlobal);
|
|
|
|
return;
|
|
}
|