/* * @Doc DMusic16 * * @Module Device.c - Device management routines | * * This module manages handles and handle instances to the legacy MIDI device. * * Each open device is represented by a handle (which is * an struct). This struct contains all of the information * concerning the state of the device, including a reference count of the * number of clients using the device. * * Each client use of one device is represented by a handle instance (which * is an struct). A near pointer to this struct is * the actual handle seen by the client. These handle instances are used * to hold any client-specific information, and to dereference client * handles to the proper struct. * * Currently we support multiple clients on the same output device but * only one client per input device. * * @globalv NPLINKNODE | gOpenHandleInstanceList | The master list of all * open handle instances. * * @globalv NPLINKNODE | gOpenHandleList | The master list of all open * handles. * * @globalv UINT | gcOpenInputDevices | A reference count of open MIDI * in devices. * * @globalv UINT | gcOpenOutputDevices | A reference count of open MIDI * out devices. */ #include #include #include "dmusic16.h" #include "debug.h" NPLINKNODE gOpenHandleInstanceList; NPLINKNODE gOpenHandleList; UINT gcOpenInputDevices; UINT gcOpenOutputDevices; STATIC VOID PASCAL UpdateSegmentLocks(BOOL fIsOutput); #pragma alloc_text(INIT_TEXT, DeviceOnLoad) #pragma alloc_text(FIX_COMM_TEXT, IsValidHandle) /* @func Called at DLL LibInit * * @comm * * Initialize the handle lists to empty and clear the device reference counts. */ VOID PASCAL DeviceOnLoad(VOID) { gOpenHandleInstanceList = NULL; gOpenHandleList = NULL; gcOpenInputDevices = 0; gcOpenOutputDevices = 0; } /* @func Open a device * * @comm * * This function is thunked to the 32-bit peer. * * This function allocates an struct on behalf of the caller. * If the requested device is already open and is an output device, the device's * reference count will be incremented an no other action is taken. If the requested * device is already open and is an input device, then the open will fail. * * If a non-DirectMusic application has the requested device open, then the * open will fail regardless of device type. * * If this open is the first input or output device opened, then it will * page lock the appropriate segments containing callback code and data. * * @rdesc Returns one of the following: * * @flag MMSYSERR_NOERROR | On success * @flag MMSYSERR_NOMEM | If there was insufficient memory to allocate * the tracking structure. * * @flag MMSYSERR_BADDEVICEID | If the given device ID was out of range. * @flag MMSYSERR_ALLOCATED | The specified device is already open. * */ MMRESULT WINAPI OpenLegacyDevice( UINT id, /* @parm MMSYSTEM id of device to open */ BOOL fIsOutput, /* @parm TRUE if this is an output device */ BOOL fShare, /* @parm TRUE if the device should be shareable */ LPHANDLE ph) /* @parm Pointer where handle will be returned */ /* on success. */ { NPOPENHANDLEINSTANCE pohi; NPLINKNODE pLink; NPOPENHANDLE poh; MMRESULT mmr; DPF(2, "OpenLegacyDevice(%d,%s,%s)", (UINT)id, (LPSTR)(fIsOutput ? "Output" : "Input"), (LPSTR)(fShare ? "Shared" : "Exclusive")); *ph = (HANDLE)NULL; /* Sharing capture device is not allowed. */ if ((!fIsOutput) && (fShare)) { return MMSYSERR_ALLOCATED; } /* Make sure id is in the valid range of devices */ if (fIsOutput) { if (id != MIDI_MAPPER && id >= midiOutGetNumDevs()) { return MMSYSERR_BADDEVICEID; } } else { if (id >= midiInGetNumDevs()) { return MMSYSERR_BADDEVICEID; } } /* Create an open handle instance. This will be returned to * Win32 as the handle. */ pohi = (NPOPENHANDLEINSTANCE)LocalAlloc(LPTR, sizeof(OPENHANDLEINSTANCE)); if (NULL == pohi) { return MMSYSERR_NOMEM; } /* Search through the handles we already have open and try * to find the handle already open. */ mmr = MMSYSERR_NOERROR; for (pLink = gOpenHandleList; pLink; pLink = pLink->pNext) { poh = (NPOPENHANDLE)pLink; if (poh->id != id) { continue; } if ((fIsOutput && (!(poh->wFlags & OH_F_MIDIIN))) || ((!fIsOutput) && (poh->wFlags & OH_F_MIDIIN))) { break; } } /* If we didn't find it, try to allocate it. * */ if (NULL == pLink) { poh = (NPOPENHANDLE)LocalAlloc(LPTR, sizeof(OPENHANDLE)); if (NULL == poh) { LocalFree((HLOCAL)pohi); return MMSYSERR_NOMEM; } poh->uReferenceCount = 1; poh->id = id; poh->wFlags = (fIsOutput ? 0 : OH_F_MIDIIN); if (fShare) { poh->wFlags |= OH_F_SHARED; } InitializeCriticalSection(&poh->wCritSect); } else { poh = (NPOPENHANDLE)pLink; /* Validate sharing modes match. * If they want exclusive mode, fail. * If the device is already open in exclusive mode, fail. */ if (!fShare) { DPF(0, "Legacy open failed: non-shared open request, port already open."); LocalFree((HLOCAL)pohi); return MIDIERR_BADOPENMODE; } if (!(poh->wFlags & OH_F_SHARED)) { DPF(0, "Legacy open failed: Port already open in exclusive mode."); LocalFree((HLOCAL)pohi); return MIDIERR_BADOPENMODE; } ++poh->uReferenceCount; } pohi->pHandle = poh; pohi->fActive = FALSE; pohi->wTask = GetCurrentTask(); /* We lock segments here so we minimize the impacy of activation. However, * actual device open is tied to activation. */ if (fIsOutput) { ++gcOpenOutputDevices; mmr = MidiOutOnOpen(pohi); if (mmr) { --gcOpenOutputDevices; } UpdateSegmentLocks(fIsOutput); } else { ++gcOpenInputDevices; mmr = MidiInOnOpen(pohi); if (mmr) { --gcOpenInputDevices; } UpdateSegmentLocks(fIsOutput); } if (poh->uReferenceCount == 1) { ListInsert(&gOpenHandleList, &poh->link); } ListInsert(&gOpenHandleInstanceList, &pohi->link); ListInsert(&poh->pInstanceList, &pohi->linkHandleList); *ph = (HANDLE)(DWORD)(WORD)pohi; return MMSYSERR_NOERROR; } /* @func Close a legacy device * * @comm * * This function is thunked to the 32-bit peer. * * It just validates the handle and calls the internal close device API. * * @rdesc Returns one of the following: * * @flag MMSYSERR_NOERROR | On success * * @flag MMSYSERR_INVALHANDLE | If the passed handle was not recognized. * */ MMRESULT WINAPI CloseLegacyDevice( HANDLE h) /* @parm The handle to close. */ { NPOPENHANDLEINSTANCE pohi = (NPOPENHANDLEINSTANCE)(WORD)h; DPF(2, "CloseLegacyDevice %04X\n", h); if (!IsValidHandle(h, VA_F_EITHER, &pohi)) { DPF(0, "CloseLegacyDevice: Invalid handle\n"); return MMSYSERR_INVALHANDLE; } return CloseLegacyDeviceI(pohi); } /* @func Activate or deactivate a legacy device * * @comm * * This function is thunked to the 32-bit peer. * * Validate parameters and pass the call to the internal activate. * * @rdesc Returns one of the following: * * @flag MMSYSERR_NOERROR | On success * @flag MMSYSERR_INVALHANDLE | If the passed handle was not recognized. * Any other MMRESULT that a midiXxx call might return. * */ MMRESULT WINAPI ActivateLegacyDevice( HANDLE h, BOOL fActivate) { NPOPENHANDLEINSTANCE pohi; if (!IsValidHandle(h, VA_F_EITHER, &pohi)) { DPF(0, "Activate: Invalid handle\n"); return MMSYSERR_INVALHANDLE; } return ActivateLegacyDeviceI(pohi, fActivate); } /* @func Close a legacy device (internal) * * @comm * * This function deallocates the referenced struct. * If it is the last reference to the device, then the device will be closed * as well. * * If this is the last input or output device being closed, then the * appropriate segments containing callback code and data will be * unlocked. * * @rdesc Returns one of the following: * * @flag MMSYSERR_NOERROR | On success * */ MMRESULT PASCAL CloseLegacyDeviceI( NPOPENHANDLEINSTANCE pohi) { NPOPENHANDLE poh; /* Deactivate this device. This might result in the device being closed. */ ActivateLegacyDeviceI(pohi, FALSE); poh = pohi->pHandle; ListRemove(&gOpenHandleInstanceList, &pohi->link); ListRemove(&poh->pInstanceList, &pohi->linkHandleList); --poh->uReferenceCount; if (poh->wFlags & OH_F_MIDIIN) { --gcOpenInputDevices; MidiInOnClose(pohi); UpdateSegmentLocks(FALSE /*fIsOutput*/); } else { --gcOpenOutputDevices; MidiOutOnClose(pohi); UpdateSegmentLocks(TRUE /*fIsOutput*/); } if (0 == poh->uReferenceCount) { ListRemove(&gOpenHandleList, &poh->link); LocalFree((HLOCAL)poh); } LocalFree((HLOCAL)pohi); return MMSYSERR_NOERROR; } /* @func Activate or deactivate a legacy device (internal) * * @comm * * This function is thunked to the 32-bit peer. * * Handle open and close of the device on first activate and last deactivate. * * @rdesc Returns one of the following: * * @flag MMSYSERR_NOERROR | On success * @flag MMSYSERR_INVALHANDLE | If the passed handle was not recognized. * Any other MMRESULT that a midiXxx call might return. * */ MMRESULT PASCAL ActivateLegacyDeviceI( NPOPENHANDLEINSTANCE pohi, BOOL fActivate) { NPOPENHANDLE poh; MMRESULT mmr; poh = pohi->pHandle; if (fActivate) { if (pohi->fActive) { DPF(0, "Activate: Activating already active handle %04X", pohi); return MMSYSERR_NOERROR; } poh->uActiveCount++; if (poh->wFlags & OH_F_MIDIIN) { mmr = MidiInOnActivate(pohi); } else { mmr = MidiOutOnActivate(pohi); } if (mmr == MMSYSERR_NOERROR) { pohi->fActive = TRUE; } else { --poh->uActiveCount; } } else { if (!pohi->fActive) { DPF(0, "Activate: Deactivating already inactive handle %04X", pohi); return MMSYSERR_NOERROR; } pohi->fActive = TRUE; poh->uActiveCount--; if (poh->wFlags & OH_F_MIDIIN) { mmr = MidiInOnDeactivate(pohi); } else { mmr = MidiOutOnDeactivate(pohi); } if (mmr == MMSYSERR_NOERROR) { pohi->fActive = FALSE; } else { --poh->uActiveCount; } } return mmr; } /* @func Validate the given handle * * @comm * * Determine if the given handle is valid, and if so, return the open handle instance. * * The handle is merely a pointer to an struct. This function, * in the debug build, will verify that the handle actually points to a struct allocated * by this DLL. In all builds, the handle type will be verified. * * @rdesc Returns one of the following: * @flag MMSYSERR_NOERROR | On success * @flag MMSYSERR_INVALHANDLE | If the given handle is invalid or of the wrong type. * */ BOOL PASCAL IsValidHandle( HANDLE h, /* @parm The handle to verify */ WORD wType, /* @parm The required type of handle. One of the following: */ /* @flag VA_F_INPUT | If the handle must specify an input device */ /* @flag VA_F_OUTPUT | If the handle must specify an output device */ /* @flag VA_F_EITHER | If either type of handle is acceptable */ NPOPENHANDLEINSTANCE FAR *lppohi) /* @parm Will contain the open handle instance on return */ { #ifdef DEBUG NPLINKNODE pLink; #endif NPOPENHANDLEINSTANCE pohi = (NPOPENHANDLEINSTANCE)(WORD)h; #ifdef DEBUG /* Find the handle instance in the global list */ for (pLink = gOpenHandleInstanceList; pLink; pLink = pLink->pNext) { DPF(2, "IsValidHandle: Theirs %04X mine %04X", (WORD)h, (WORD)pLink); if (pLink == (NPLINKNODE)(WORD)h) { break; } } if (NULL == pLink) { return FALSE; } #endif DPF(2, "IsValidHandle: Got handle, flags are %04X", pohi->pHandle->wFlags); *lppohi = pohi; /* Verify the handle type */ if (pohi->pHandle->wFlags & OH_F_MIDIIN) { if (wType & VA_F_INPUT) { return TRUE; } } else { if (wType & VA_F_OUTPUT) { return TRUE; } } *lppohi = NULL; return FALSE; } /* @func Lock or unlock segments as need be. * * @comm * * This function calls the DLL's Lock and Unlock functions to bring the lock status * of the segments containing callback code and data into sync with the actual types * of devices currently open. This prevents having too much memory page locked when * it is not actually being used. * */ STATIC VOID PASCAL UpdateSegmentLocks( BOOL fIsOutput) /* @parm TRUE if the last device opened or closed was an output device */ { if (fIsOutput) { switch(gcOpenOutputDevices) { case 0: if (gcOpenInputDevices) { DPF(2, "Unlocking output"); UnlockCode(LOCK_F_OUTPUT); } else { DPF(2, "Unlocking output+common"); UnlockCode(LOCK_F_OUTPUT | LOCK_F_COMMON); } break; case 1: if (gcOpenInputDevices) { DPF(2, "Locking output"); LockCode(LOCK_F_OUTPUT); } else { DPF(2, "Locking output+common"); LockCode(LOCK_F_OUTPUT | LOCK_F_COMMON); } break; } } else { switch(gcOpenInputDevices) { case 0: if (gcOpenOutputDevices) { DPF(2, "Unlocking input"); UnlockCode(LOCK_F_INPUT); } else { DPF(2, "Unlocking input+common"); UnlockCode(LOCK_F_INPUT | LOCK_F_COMMON); } break; case 1: if (gcOpenOutputDevices) { DPF(2, "Locking input"); LockCode(LOCK_F_INPUT); } else { DPF(2, "Locking input+common"); LockCode(LOCK_F_INPUT | LOCK_F_COMMON); } break; } } } /* @func Clean up all open handles held by a given task * * @comm This function is called when a task terminates. It will clean up resources left * behind by a process which did not terminate cleanly, and therefore did not tell * this DLL to unload in its context. */ VOID PASCAL CloseDevicesForTask( WORD wTask) { NPLINKNODE pLink; NPOPENHANDLEINSTANCE pohi; for (pLink = gOpenHandleInstanceList; pLink; pLink = pLink->pNext) { pohi = (NPOPENHANDLEINSTANCE)pLink; if (pohi->wTask != wTask) { continue; } DPF(0, "CloseDevicesForTask: Closing %04X", (WORD)pohi); /* NOTE: This will free pohi */ CloseLegacyDeviceI(pohi); pLink = gOpenHandleInstanceList; } }