/***************************************************************************** * * 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 * 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 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. 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 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 structure to validate. * * @returns * * if acquisition can be stolen, or * 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 * * if all is well. * * 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; }