windows-nt/Source/XPSP1/NT/multimedia/directx/dinput/dx8/dll/diexcl.c

707 lines
18 KiB
C
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
/*****************************************************************************
*
* 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;
}