windows-nt/Source/XPSP1/NT/multimedia/directx/dmusic/dmusic16/device.c
2020-09-26 16:20:57 +08:00

642 lines
16 KiB
C

/*
* @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 <c OPENHANDLE> 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 <c OPENHANDLINSTANCE> 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 <c OPENHANDLE> 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 <windows.h>
#include <mmsystem.h>
#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 <c OPENHANDLEINSTANCE> 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 <c OPENHANDLEINSTANCE> 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 <c OPENHANDLEINSTANCE> 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;
}
}