NT4/private/windows/media/winmm/mci.c
2020-09-30 17:12:29 +02:00

3061 lines
97 KiB
C

/*******************************************************************************
*
* Module Name: mci.c
*
* Media Control Architecture Driver Interface
*
* Contents: MCI external message API's mciSendString and mciSendCommand
* Author: DLL (DavidLe)
* Created: 2/13/90
* 5/22/91: Ported to Win32 - NigelT
*
* Copyright (c) 1992 - 1995 Microsoft Corporation. All rights reserved
*
\******************************************************************************/
#define INCL_WINMM
#include "winmmi.h"
#include "mci.h"
#include "wchar.h"
/*
* MCI critical section stuff
*/
#if DBG
UINT cmciCritSec; // enter'ed count
UINT uCritSecOwner; // Thread id of critical section owner
#endif
CRITICAL_SECTION mciCritSec; // used to protect process global mci variables
#if DBG
int mciDebugLevel;
#endif
extern DWORD mciWindowThreadId;
#define MCIERR_AUTO_ALREADY_CLOSED ((MCIERROR)0xFF000000) // Secret return code
STATICFN UINT mciConvertReturnValue(
UINT uType, UINT uErr, MCIDEVICEID wDeviceID,
LPDWORD dwParams, LPWSTR lpstrReturnString,
UINT uReturnLength);
STATICFN DWORD mciSendStringInternal(
LPCWSTR lpstrCommand, LPWSTR lpstrReturnString, UINT uReturnLength,
HANDLE hCallback, LPMCI_SYSTEM_MESSAGE lpMessage);
STATICFN DWORD mciSendSystemString(
LPCWSTR lpstrCommand, DWORD dwAdditionalFlags, LPWSTR lpstrReturnString,
UINT uReturnLength);
UINT mciBreakKeyYieldProc ( MCIDEVICEID wDeviceID,
DWORD dwYieldData);
extern UINT FAR mciExtractTypeFromID(
LPMCI_OPEN_PARMSW lpOpen);
// This macro defines the list of messages for which mciSendString
// will not try to auto-open
#define MCI_CANNOT_AUTO_OPEN(wMessage) \
(wMessage == MCI_OPEN || wMessage == MCI_SYSINFO \
|| wMessage == MCI_SOUND || wMessage == MCI_CLOSE \
|| wMessage == MCI_BREAK)
// This macro devices the list of message which do not require an open
// device. It is a subset of MCI_CANNOT_AUTO_OPEN
#define MCI_DO_NOT_NEED_OPEN(wMessage) \
(wMessage == MCI_OPEN || wMessage == MCI_SOUND || wMessage == MCI_SYSINFO)
// Strings used in mciAutoOpenDevice
WSZCODE wszOpen[] = L"open";
STATICDT WSZCODE wszClose[] = L"close";
STATICDT WSZCODE wszNotify[] = L"notify"; // IMPORTANT: MUST be lowercase
STATICDT WSZCODE wszWait[] = L"wait";
STATICDT WSZCODE szCmdFormat[] = L"%ls %ls";
STATICDT WSZCODE szLongFormat[] = L"%ld";
STATICDT WSZCODE szRectFormat[] = L"%d %d %d %d";
// Special device name
STATICDT WSZCODE wszNew[] = L"new";
/*****************************************************************************
* @doc INTERNAL
*
* @func void | MciNotify | called by mmWndProc when it recives a
* MM_MCINOTIFY message
* @rdesc None.
*
****************************************************************************/
void MciNotify(
DWORD wParam,
LONG lParam)
{
//
// wParam is the notify status
// lParam is the MCI device id
//
mciEnter("MciNotify");
if (MCI_VALID_DEVICE_ID((UINT)lParam) // If a valid device
&& !(ISCLOSING(MCI_lpDeviceList[lParam]))) { // and if not in process of closing
SETAUTOCLOSING(MCI_lpDeviceList[lParam]);
//
// Must not hold MCI critical section when calling mciCloseDevice
// because DrvClose gets the load/unload critical section while
// drivers loading will have the load/unload critical section
// but can call back (eg) to mciRegisterCommandTable causing
// a deadlock.
//
// Even if the incoming notification is ABORTED/SUPERSEDED/FAILED
// we must still close the device. Otherwise devices get left open.
// mciCloseDevice will protect against trying to close a device that
// we do not own.
mciLeave("MciNotify");
mciCloseDevice ((MCIDEVICEID)lParam, 0L, NULL, TRUE);
} else {
mciLeave("MciNotify");
}
}
/*--------------------------------------------------------------------*\
* HandleNotify
*
\*--------------------------------------------------------------------*/
STATICFN void HandleNotify(
DWORD uErr,
MCIDEVICEID wDeviceID,
DWORD dwFlags,
DWORD dwParam2)
{
LPMCI_GENERIC_PARMS lpGeneric = (LPMCI_GENERIC_PARMS)dwParam2;
HANDLE hCallback;
if (0 == uErr
&& dwFlags & MCI_NOTIFY
&& lpGeneric != NULL
&& (hCallback = (HANDLE)lpGeneric->dwCallback) != NULL)
{
mciDriverNotify (hCallback, wDeviceID, MCI_NOTIFY_SUCCESSFUL);
}
}
#if DBG
/*--------------------------------------------------------------------*\
* mciDebugOut
*
* Dump the string form of an MCI command
\*--------------------------------------------------------------------*/
UINT NEAR mciDebugOut(
MCIDEVICEID wDeviceID,
UINT wMessage,
DWORD dwFlags,
DWORD dwParam2,
LPMCI_DEVICE_NODE nodeWorking)
{
LPWSTR lpCommand, lpFirstParameter, lpPrevious, lszDebugOut;
WCHAR strTemp[256];
UINT wID;
UINT wOffset, wOffsetFirstParameter;
UINT uReturnType = 0;
DWORD dwValue;
DWORD dwMask = 1; // used to test each flag bit in turn
UINT wTable;
// Find the command table for the given command message ID
lpCommand = FindCommandItem( wDeviceID, NULL, (LPWSTR)wMessage,
NULL, &wTable );
if (lpCommand == NULL)
{
if (wMessage != MCI_OPEN_DRIVER && wMessage != MCI_CLOSE_DRIVER) {
ROUT(("WINMM: mciDebugOut: Command table not found"));
}
return 0;
}
lszDebugOut = mciAlloc( BYTE_GIVEN_CHAR( 512 ) );
if (!lszDebugOut) {
ROUT(("WINMM: Not enough memory to display command"));
return 0;
}
// Dump the command name into the buffer
wsprintfW( lszDebugOut, L"MCI command: \"%ls", lpCommand );
// Dump the device name
if (wDeviceID == MCI_ALL_DEVICE_ID)
{
wcscat( lszDebugOut, L" all" );
}
else if (nodeWorking != NULL)
{
if (nodeWorking->dwMCIOpenFlags & MCI_OPEN_ELEMENT_ID)
{
wsprintfW( lszDebugOut + wcslen( lszDebugOut ),
L" Element ID:0x%lx", nodeWorking->dwElementID );
}
else if (nodeWorking->lpstrName != NULL)
{
wsprintfW( lszDebugOut + wcslen( lszDebugOut ),
L" %ls", nodeWorking->lpstrName );
}
}
// Skip past command entry
lpCommand = (LPWSTR)((LPBYTE)lpCommand + mciEatCommandEntry( lpCommand, NULL, NULL));
// Get the next entry
lpFirstParameter = lpCommand;
// Skip past the DWORD return value
wOffsetFirstParameter = 4;
lpCommand = (LPWSTR)((LPBYTE)lpCommand +
mciEatCommandEntry( lpCommand,
&dwValue, &wID ));
// If it is a return value, skip it
if (wID == MCI_RETURN)
{
uReturnType = (UINT)dwValue;
lpFirstParameter = lpCommand;
wOffsetFirstParameter += mciGetParamSize (dwValue, wID);
lpCommand = (LPWSTR)((LPBYTE)lpCommand +
mciEatCommandEntry(lpCommand,
&dwValue, &wID));
}
// Dump device name parameter to OPEN
if (wMessage == MCI_OPEN)
{
LPCWSTR lpstrDeviceType =
((LPMCI_OPEN_PARMSW)dwParam2)->lpstrDeviceType;
LPCWSTR lpstrElementName =
((LPMCI_OPEN_PARMSW)dwParam2)->lpstrElementName;
// Tack on device type
if (dwFlags & MCI_OPEN_TYPE_ID)
{
LPMCI_OPEN_PARMSW lpOpen = (LPMCI_OPEN_PARMSW)dwParam2;
DWORD dwOld = (DWORD)lpOpen->lpstrDeviceType;
if (mciExtractTypeFromID ((LPMCI_OPEN_PARMSW)dwParam2) != 0) {
strTemp[0] = '\0';
}
wcscpy (strTemp, (LPWSTR)lpOpen->lpstrDeviceType);
mciFree ((LPWSTR)lpOpen->lpstrDeviceType);
lpOpen->lpstrDeviceType = (LPWSTR)dwOld;
} else if (lpstrDeviceType != NULL)
wcscpy (strTemp, (LPWSTR)lpstrDeviceType);
else {
strTemp[0] = '\0';
}
if (dwFlags & MCI_OPEN_ELEMENT_ID)
{
// Tack on element ID
wcscat( strTemp, L" Element ID:");
wsprintfW( strTemp + wcslen (strTemp), szLongFormat,
LOWORD ((DWORD)lpstrDeviceType));
} else
{
// Add separator if both type name and element name are present
if (lpstrDeviceType != 0 && lpstrElementName != 0) {
wcscat( strTemp, L"!" );
}
if (lpstrElementName != 0 && dwFlags & MCI_OPEN_ELEMENT) {
wcscat( strTemp, lpstrElementName );
}
}
wsprintfW( lszDebugOut + wcslen(lszDebugOut), L" %ls", strTemp );
}
// Walk through each flag
while (dwMask != 0)
{
// Is this bit set?
if ((dwFlags & dwMask) != 0 && !
// The MCI_OPEN_TYPE and MCI_OPEN_ELEMENT flags are taken care of
// above
(wMessage == MCI_OPEN && (dwMask == MCI_OPEN_TYPE
|| dwMask == MCI_OPEN_ELEMENT)))
{
lpPrevious = lpCommand = lpFirstParameter;
wOffset = 0;
lpCommand = (LPWSTR)((LPBYTE)lpCommand
+ mciEatCommandEntry( lpCommand, &dwValue, &wID ));
// What parameter uses this bit?
while (wID != MCI_END_COMMAND && dwValue != dwMask)
{
wOffset += mciGetParamSize( dwValue, wID);
if (wID == MCI_CONSTANT) {
while (wID != MCI_END_CONSTANT) {
lpCommand = (LPWSTR)((LPBYTE)lpCommand
+ mciEatCommandEntry( lpCommand, NULL, &wID));
}
}
lpPrevious = lpCommand;
lpCommand = (LPWSTR)((LPBYTE)lpCommand
+ mciEatCommandEntry( lpCommand, &dwValue, &wID ));
}
if (wID != MCI_END_COMMAND)
{
// Found the parameter which matches this flag bit
// Print the parameter name
if (*lpPrevious) {
wsprintfW( lszDebugOut + wcslen(lszDebugOut),
L" %ls", lpPrevious);
}
// Print any argument
switch (wID)
{
case MCI_STRING:
wsprintfW( lszDebugOut + wcslen(lszDebugOut),
L" %ls", *(LPWSTR *)( (LPBYTE)dwParam2
+ wOffset + wOffsetFirstParameter) );
break;
case MCI_CONSTANT:
{
DWORD dwConst = *(LPDWORD)((LPBYTE)dwParam2 + wOffset +
wOffsetFirstParameter);
UINT wLen;
BOOL bFound = FALSE;
while (wID != MCI_END_CONSTANT)
{
wLen = mciEatCommandEntry( lpCommand,
&dwValue, &wID);
if (dwValue == dwConst)
{
bFound = TRUE;
wsprintfW( lszDebugOut + wcslen(lszDebugOut),
L" %ls", lpCommand);
}
lpCommand = (LPWSTR)((LPBYTE)lpCommand + wLen);
}
if (bFound)
break;
// FALL THROUGH
}
case MCI_INTEGER:
case MCI_HWND:
case MCI_HPAL:
case MCI_HDC:
wsprintfW( strTemp, szLongFormat,
*(LPDWORD)((LPBYTE)dwParam2 + wOffset +
wOffsetFirstParameter));
wsprintfW( lszDebugOut + wcslen(lszDebugOut),
L" %ls", strTemp );
break;
}
}
}
// Go to the next flag
dwMask <<= 1;
}
mciUnlockCommandTable( wTable);
wcscat(lszDebugOut, L"\"" );
ROUTSW((lszDebugOut));
mciFree(lszDebugOut);
return uReturnType;
}
#endif
DWORD mciBreak(
MCIDEVICEID wDeviceID,
DWORD dwFlags,
LPMCI_BREAK_PARMS lpBreakon)
{
HWND hwnd;
if (dwFlags & MCI_BREAK_KEY)
{
if (dwFlags & MCI_BREAK_OFF) {
return MCIERR_FLAGS_NOT_COMPATIBLE;
}
if (dwFlags & MCI_BREAK_HWND) {
hwnd = lpBreakon->hwndBreak;
}
else
{
hwnd = NULL;
}
return mciSetBreakKey (wDeviceID, lpBreakon->nVirtKey,
hwnd)
? 0 : MMSYSERR_INVALPARAM;
} else if (dwFlags & MCI_BREAK_OFF) {
mciSetYieldProc (wDeviceID, NULL, 0);
return 0;
} else {
return MCIERR_MISSING_PARAMETER;
}
}
//***********************************************************************
// mciAutoCloseDevice
//
// Close the indicated device by sending a message inter-task
//***********************************************************************
STATICFN DWORD mciAutoCloseDevice(
LPCWSTR lpstrDevice)
{
LPWSTR lpstrCommand;
DWORD dwRet;
int alloc_len = BYTE_GIVEN_CHAR( wcslen( lpstrDevice) ) +
sizeof(wszClose) + sizeof(WCHAR);
if ((lpstrCommand = mciAlloc ( alloc_len ) ) == NULL)
return MCIERR_OUT_OF_MEMORY;
wsprintfW( lpstrCommand, szCmdFormat, wszClose, lpstrDevice);
dwRet = mciSendSystemString( lpstrCommand, 0L, NULL, 0);
mciFree( lpstrCommand);
return dwRet;
}
//***********************************************************************
// mciSendSingleCommand
//
// Process a single MCI command
// Called by mciSendCommandInternal
//
//***********************************************************************
DWORD NEAR mciSendSingleCommand(
MCIDEVICEID wDeviceID,
UINT wMessage,
DWORD dwParam1,
DWORD dwParam2,
LPMCI_DEVICE_NODE nodeWorking,
BOOL bWalkAll,
LPMCI_INTERNAL_OPEN_INFO lpOpenInfo)
{
DWORD dwRet;
#if DBG
UINT uReturnType;
if (mciDebugLevel != 0)
uReturnType = mciDebugOut( wDeviceID, wMessage, dwParam1, dwParam2,
nodeWorking);
if (nodeWorking == NULL && !MCI_DO_NOT_NEED_OPEN (wMessage))
return MCIERR_INTERNAL;
#endif
switch (wMessage)
{
case MCI_OPEN:
dwRet = mciOpenDevice (dwParam1,
(LPMCI_OPEN_PARMSW)dwParam2, lpOpenInfo);
break;
case MCI_CLOSE:
// If we were walking the device list and this device was auto opened
// send the command via a task switch
// If we just called mciCloseDevice (as sometimes happened before a bug
// was fixed mciCloseDevice will unload the driver but the MCI_CLOSE_DRIVER
// command will not get sent because it will be rejected as coming from the
// wrong task. The result would be (was) that the driver would access violate
// when it next did something.
if (GetCurrentTask() != nodeWorking->hCreatorTask)
{
LPWSTR lpstrCommand;
if (!bWalkAll) {
//
// Only valid to close an auto-opened device if it's
// being close as part of closing MCI_ALL_DEVICE_ID
// We can reach here if an app 'guesses' an MCI device
// id and tries to close it while playing.
//
dwRet = MCIERR_ILLEGAL_FOR_AUTO_OPEN;
break;
}
lpstrCommand = mciAlloc( sizeof(wszClose)+ sizeof(WCHAR) +
BYTE_GIVEN_CHAR( wcslen( nodeWorking->lpstrName ) ) );
if ( lpstrCommand == NULL )
return MCIERR_OUT_OF_MEMORY;
wcscpy( lpstrCommand, wszClose);
wcscat( lpstrCommand, L" ");
wcscat( lpstrCommand, nodeWorking->lpstrName);
dwRet = mciSendSystemString( lpstrCommand, 0L, NULL, 0);
mciFree( lpstrCommand);
} else
dwRet = mciCloseDevice( wDeviceID, dwParam1,
(LPMCI_GENERIC_PARMS)dwParam2, TRUE);
break;
case MCI_SYSINFO:
dwRet = mciSysinfo( wDeviceID, dwParam1,
(LPMCI_SYSINFO_PARMSW)dwParam2);
HandleNotify( dwRet, wDeviceID, dwParam1, dwParam2);
break;
case MCI_BREAK:
dwRet = mciBreak( wDeviceID, dwParam1,
(LPMCI_BREAK_PARMS)dwParam2);
HandleNotify( dwRet, wDeviceID, dwParam1, dwParam2);
break;
case MCI_SOUND:
{
LPMCI_SOUND_PARMSW lpSound = (LPMCI_SOUND_PARMSW)dwParam2;
if ( PlaySoundW( MCI_SOUND_NAME & dwParam1
? lpSound->lpstrSoundName
: L".Default",
(HANDLE)0,
dwParam1 & MCI_WAIT
? SND_SYNC | SND_ALIAS
: SND_ASYNC | SND_ALIAS ) )
{
dwRet = 0;
} else {
dwRet = MCIERR_HARDWARE;
}
HandleNotify( dwRet, wDeviceID, dwParam1, dwParam2);
break;
}
default:
#if 0 // don't bother (NigelT)
if (mciDebugLevel > 1)
{
dwStartTime = timeGetTime();
}
#endif
// Initialize GetAsyncKeyState for break key
{
if ((dwParam1 & MCI_WAIT) &&
nodeWorking->fpYieldProc == mciBreakKeyYieldProc)
{
dprintf4(("Getting initial state of Break key"));
GetAsyncKeyState( nodeWorking->dwYieldData);
//GetAsyncKeyState( LOWORD(nodeWorking->dwYieldData));
}
}
dwRet = DrvSendMessage( nodeWorking->hDrvDriver, wMessage,
dwParam1, dwParam2);
break;
} // switch
#if DBG
if (mciDebugLevel != 0)
{
if (dwRet & MCI_INTEGER_RETURNED)
uReturnType = MCI_INTEGER;
switch (uReturnType)
{
case MCI_INTEGER:
{
WCHAR strTemp[50];
mciConvertReturnValue( uReturnType, HIWORD(dwRet), wDeviceID,
(LPDWORD)dwParam2, strTemp,
CHAR_GIVEN_BYTE( sizeof(strTemp) ) );
dprintf2((" returns: %ls", strTemp));
break;
}
case MCI_STRING:
dprintf2((" returns: %ls",(LPWSTR)(1 + (LPDWORD)dwParam2)));
break;
}
}
#endif
return dwRet;
}
//***********************************************************************
// mciSendCommandInternal
//
// Internal version of mciSendCommand. Differs ONLY in that the return
// value is a DWORD where the high word has meaning only for mciSendString
//
//***********************************************************************
STATICFN DWORD mciSendCommandInternal(
MCIDEVICEID wDeviceID,
UINT wMessage,
DWORD dwParam1,
DWORD dwParam2,
LPMCI_INTERNAL_OPEN_INFO lpOpenInfo)
{
DWORD dwRetVal;
LPMCI_DEVICE_NODE nodeWorking = NULL;
BOOL bWalkAll;
DWORD dwAllError = 0;
HANDLE hCurrentTask;
hCurrentTask = GetCurrentTask();
// If the device is "all" and the message is *not*
// "sysinfo" then we must walk all devices
if (wDeviceID == MCI_ALL_DEVICE_ID
&& (wMessage != MCI_SYSINFO)
&& (wMessage != MCI_SOUND))
{
if (wMessage == MCI_OPEN)
{
dwRetVal = MCIERR_CANNOT_USE_ALL;
goto exitfn;
}
bWalkAll = TRUE;
// Start at device #1
wDeviceID = 1;
} else {
bWalkAll = FALSE;
}
mciEnter("mciSendCommandInternal");
// Walk through all devices if bWalkAll or just one device if !bWalkAll
do
{
// Initialize
dwRetVal = 0;
// Validate the device ID if single device
if (!bWalkAll)
{
if (!MCI_DO_NOT_NEED_OPEN(wMessage))
{
if (!MCI_VALID_DEVICE_ID(wDeviceID))
{
dwRetVal = MCIERR_INVALID_DEVICE_ID;
goto exitfn;
}
nodeWorking = MCI_lpDeviceList[wDeviceID];
}
}
else if (wMessage != MCI_SYSINFO)
{
nodeWorking = MCI_lpDeviceList[wDeviceID];
}
// Skip if walking the device list and the
// device is not part of the current task
if (bWalkAll)
{
if (nodeWorking == NULL ||
nodeWorking->hOpeningTask != hCurrentTask)
goto no_send;
}
// If the device is in the process of closing and the message
// is not MCI_CLOSE_DEVICE then return an error
if (nodeWorking != NULL &&
ISCLOSING(nodeWorking) &&
wMessage != MCI_CLOSE_DRIVER)
{
dwRetVal = MCIERR_DEVICE_LOCKED;
goto exitfn;
}
// If this message is being sent from the wrong task (the device was auto-
// opened) fail all but the MCI_CLOSE message which gets sent inter-task
if (nodeWorking != NULL &&
nodeWorking->hCreatorTask != hCurrentTask)
{
if (wMessage != MCI_CLOSE)
{
dwRetVal = MCIERR_ILLEGAL_FOR_AUTO_OPEN;
goto exitfn;
}
else
{
// Don't even allow close from mciSendCommand if auto-open device has a
// pending close
if (ISAUTOCLOSING(nodeWorking))
{
dwRetVal = MCIERR_DEVICE_LOCKED;
goto exitfn;
}
}
}
mciLeave("mciSendCommandInternal");
dwRetVal = mciSendSingleCommand( wDeviceID, wMessage, dwParam1,
dwParam2, nodeWorking, bWalkAll,
lpOpenInfo);
mciEnter("mciSendCommandInternal");
no_send:
// If we are processing multiple devices
if (bWalkAll)
{
// If there was an error for this device
if (dwRetVal != 0)
{
// If this is not the first error
if (dwAllError != 0) {
dwAllError = MCIERR_MULTIPLE;
// Just one error so far
} else {
dwAllError = dwRetVal;
}
}
}
} while (bWalkAll && ++wDeviceID < MCI_wNextDeviceID);
exitfn:;
mciLeave("mciSendCommandInternal");
return dwAllError == MCIERR_MULTIPLE ? dwAllError : dwRetVal;
}
/************************************************************************
* @doc EXTERNAL MCI
*
* @api DWORD | mciSendCommand | This function sends a command message to
* the specified MCI device.
*
* @parm MCIDEVICEID | wDeviceID | Specifies the device ID of the MCI device
* to receive the command. This parameter is
* not used with the <m MCI_OPEN> command.
*
* @parm UINT | wMessage | Specifies the command message.
*
* @parm DWORD | dwParam1 | Specifies flags for the command.
*
* @parm DWORD | dwParam2 | Specifies a pointer to a parameter block
* for the command.
*
* @rdesc Returns zero if the function was successful. Otherwise, it returns
* error information. The low-order word
* of the returned DWORD is the error return value. If the error is
* device-specific, the high-order word contains the driver ID; otherwise
* the high-order word is zero.
*
* To get a textual description of <f mciSendCommand> return values,
* pass the return value to <f mciGetErrorString>.
*
* Error values that are returned when a device is being opened
* are listed with the MCI_OPEN message. In addition to the
* MCI_OPEN error returns, this function can
* return the following values:
*
* @flag MCIERR_BAD_TIME_FORMAT | Illegal value for time format.
*
* @flag MCIERR_CANNOT_USE_ALL | The device name "all" is not allowed
* for this command.
*
* @flag MCIERR_CREATEWINDOW | Could not create or use window.
*
* @flag MCIERR_DEVICE_LOCKED | The device is locked until it is
* closed automatically.
*
* @flag MCIERR_DEVICE_NOT_READY | Device not ready.
*
* @flag MCIERR_DEVICE_TYPE_REQUIRED | The device name must be a valid
* device type.
*
* @flag MCIERR_DRIVER | Unspecified device error.
*
* @flag MCIERR_DRIVER_INTERNAL | Internal driver error.
*
* @flag MCIERR_FILE_NOT_FOUND | Requested file not found.
*
* @flag MCIERR_FILE_NOT_SAVED | The file was not saved.
*
* @flag MCIERR_FILE_READ | A read from the file failed.
*
* @flag MCIERR_FILE_WRITE | A write to the file failed.
*
* @flag MCIERR_FLAGS_NOT_COMPATIBLE | Incompatible parameters
* were specified.
*
* @flag MCIERR_HARDWARE | Hardware error on media device.
*
* @flag MCIERR_INTERNAL | Internal error.
*
* @flag MCIERR_INVALID_DEVICE_ID | Invalid device ID.
*
* @flag MCIERR_INVALID_DEVICE_NAME | The device is not open
* or is not known.
*
* @flag MCIERR_INVALID_FILE | Invalid file format.
*
* @flag MCIERR_MULTIPLE | Errors occurred in more than one device.
*
* @flag MCIERR_NO_WINDOW | There is no display window.
*
* @flag MCIERR_NULL_PARAMETER_BLOCK | Parameter block pointer was NULL.
*
* @flag MCIERR_OUT_OF_MEMORY | Not enough memory for requested operation.
*
* @flag MCIERR_OUTOFRANGE | Parameter value out of range.
*
* @flag MCIERR_UNNAMED_RESOURCE | Attempt to save unnamed file.
*
* @flag MCIERR_UNRECOGNIZED_COMMAND | Unknown command.
*
* @flag MCIERR_UNSUPPORTED_FUNCTION | Action not available for this
* device.
*
* The following additional return values are defined for MCI sequencers:
*
* @flag MCIERR_SEQ_DIV_INCOMPATIBLE | Set Song Pointer incompatible
* with SMPTE files.
*
* @flag MCIERR_SEQ_PORT_INUSE | Specified port is in use.
*
* @flag MCIERR_SEQ_PORT_MAPNODEVICE | Current map uses non-existent
* device.
*
* @flag MCIERR_SEQ_PORT_MISCERROR | Miscellaneous error with
* specified port.
*
* @flag MCIERR_SEQ_PORT_NONEXISTENT | Specified port does not exist.
*
* @flag MCIERR_SEQ_PORTUNSPECIFIED | No current MIDI port.
*
* @flag MCIERR_SEQ_NOMIDIPRESENT | No MIDI ports present.
*
* @flag MCIERR_SEQ_TIMER | Timer error.
*
* The following additional return values are defined for MCI waveform
* audio devices:
*
* @flag MCIERR_WAVE_INPUTSINUSE | No compatible waveform recording
* device is free.
*
* @flag MCIERR_WAVE_INPUTSUNSUITABLE | No compatible waveform
* recording devices.
*
* @flag MCIERR_WAVE_INPUTUNSPECIFIED | Any compatible waveform
* recording device may be used.
*
* @flag MCIERR_WAVE_OUTPUTSINUSE | No compatible waveform playback
* device is free.
*
* @flag MCIERR_WAVE_OUTPUTSUNSUITABLE | No compatible waveform
* playback devices.
*
* @flag MCIERR_WAVE_OUTPUTUNSPECIFIED | Any compatible waveform
* playback device may be used.
*
* @flag MCIERR_WAVE_SETINPUTINUSE | Set waveform recording device
* is in use.
*
* @flag MCIERR_WAVE_SETINPUTUNSUITABLE | Set waveform recording
* device is incompatible with set format.
*
* @flag MCIERR_WAVE_SETOUTPUTINUSE | Set waveform playback device
* is in use.
*
* @flag MCIERR_WAVE_SETOUTPUTUNSUITABLE | Set waveform playback
* device is incompatible with set format.
*
* @comm Use the <m MCI_OPEN> command to obtain the device ID
* specified by <p wDeviceID>.
*
* @xref mciGetErrorString mciSendString
*/
/*
* @doc internal
*
* @api DWORD | mciDriverEntry | Actually a callback. The entry point for MCI drivers.
*
* @parm UINT | wMessage | Identifies the requested action to be performed.
*
* @parm DWORD | dwParam1 | Specifies data for this message. Defined separately
* for each message.
*
* @parm DWORD | dwParam2 | Specifies data for this message. Defined separately
* for each message.
*
* @rdesc The return value is defined separately for each message.
*/
DWORD mciSendCommandA(
MCIDEVICEID wDeviceID,
UINT wMessage,
DWORD dwParam1,
DWORD dwParam2)
{
LPCSTR lpStr1;
LPCSTR lpStr2;
LPCSTR lpStr3;
DWORD dwRet;
/*
** If dwParam1 is 0L, we have no information to perform the ascii
** to unicode thunks from. Therefore, I will pass the call straight
** thru to mciSendCommandW "as is".
*/
if ( dwParam1 == 0L ) {
return mciSendCommandW( wDeviceID, wMessage, dwParam1, dwParam2 );
}
/*
** If we are still here we have some thunking to do.
**
**
** Basically this code is very similiar to the WOW thunk code.
**
** We have to special case MCI_OPEN and MCI_SYSINFO because the
** command table is either not available or in an inconsistent state.
**
** Otherwise, the code is identical to the WOW code. Maybe we could do
** unicode thunking in the WOW layer and then call mciSendCommandW.
** It seems bad that we should have to thunk poor old WOW apps twice!!
** they are slow enough as it is :-)
**
** We have the advantage that all pointers are already 32 bit.
**
*/
switch ( wMessage ) {
case MCI_CLOSE_DRIVER:
dprintf3(( "MCI_CLOSE_DRIVER command" ));
return mciSendCommandW( wDeviceID, wMessage, dwParam1, dwParam2 );
break;
case MCI_OPEN_DRIVER:
dprintf3(( "MCI_OPEN_DRIVER command" ));
/* fall thru */
case MCI_OPEN:
{
LPMCI_OPEN_PARMSW lpOpenP = (LPMCI_OPEN_PARMSW)dwParam2;
#if DBG
dprintf3(( "MCI_OPEN command" ));
/*
** As of yet I don't know how to thunk command extensions
** for the open command.
** These may well contain strings but we have no way of
** knowing because we haven't got access to the command table.
*/
if ( dwParam1 & 0xFFFF0000 ) {
dprintf1(( "MCI_OPEN called with command extensions !!" ));
}
#endif
/*
** First save the original ascii string pointers.
** Note that lpstrDeviceType may be a TYPE_ID
** Note that lpstrElementName may be a ELEMENT_ID
*/
lpStr1 = (LPCSTR)lpOpenP->lpstrDeviceType;
lpStr2 = (LPCSTR)lpOpenP->lpstrElementName;
lpStr3 = (LPCSTR)lpOpenP->lpstrAlias;
/*
** Now allocate a unicode copy of the ascii, don't try
** to copy NULL strings, or ID types
**
** The first string to be copied is lpstrDeviceType.
** This pointer is only valid if the MCI_OPEN_TYPE bit
** is set and MCI_OPEN_TYPE_ID is not set. If either
** bit is set and lpstrDeviceType is NULL it is an
** error that will be picked up later.
**
** The second string is lpstrElementName which is valid
** only with MCI_OPEN_ELEMENT set and MCI_OPEN_ELEMENT_ID
** not set. As in the case above it is an error if
** either bit is set but the pointer itself is NULL.
**
** The third string is lpstrAlias which is valid only
** with MCI_OPEN_ALIAS set. In this case when this bit
** is set there is no modifying bit that changes the
** meaning of the pointer.
**
** If an unicode string is not allocated the internal
** pointer is set to NULL. This value can be checked
** after the mciSendCommand call to see if the string
** has to be freed and the original pointer restored.
*/
if ( lpStr1 ) {
if ((dwParam1 & MCI_OPEN_TYPE)
&& !(dwParam1 & MCI_OPEN_TYPE_ID) ) {
lpOpenP->lpstrDeviceType = AllocUnicodeStr( (LPSTR)lpStr1 );
if ( lpOpenP->lpstrDeviceType == NULL ) {
dwRet = MCIERR_OUT_OF_MEMORY;
goto err1;
}
} else lpStr1 = NULL; // Nothing allocated, will free nothing
}
if ( lpStr2 ) {
if ((dwParam1 & MCI_OPEN_ELEMENT)
&& !(dwParam1 & MCI_OPEN_ELEMENT_ID) ) {
lpOpenP->lpstrElementName = AllocUnicodeStr( (LPSTR)lpStr2 );
if ( lpOpenP->lpstrElementName == NULL ) {
dwRet = MCIERR_OUT_OF_MEMORY;
goto err2;
}
} else lpStr2 = NULL; // Nothing allocated, will free nothing
}
if ( lpStr3 ) {
if (dwParam1 & MCI_OPEN_ALIAS) {
lpOpenP->lpstrAlias = AllocUnicodeStr( (LPSTR)lpStr3 );
if ( lpOpenP->lpstrAlias == NULL ) {
dwRet = MCIERR_OUT_OF_MEMORY;
goto err3;
}
} else lpStr3 = NULL; // Nothing allocated, will free nothing
}
/*
** Now call the unicode version
*/
dwRet = mciSendCommandW( wDeviceID, wMessage, dwParam1, dwParam2 );
/*
** Free the unicode strings.
** and restore the original string pointers
*/
if ( lpStr3 ) {
FreeUnicodeStr( (LPWSTR)lpOpenP->lpstrAlias );
err3: lpOpenP->lpstrAlias = (LPCWSTR)lpStr3;
}
if ( lpStr2 ) {
FreeUnicodeStr( (LPWSTR)lpOpenP->lpstrElementName );
err2: lpOpenP->lpstrElementName = (LPCWSTR)lpStr2;
}
if ( lpStr1 ) {
FreeUnicodeStr( (LPWSTR)lpOpenP->lpstrDeviceType );
err1: lpOpenP->lpstrDeviceType = (LPCWSTR)lpStr1;
}
return dwRet;
}
case MCI_SYSINFO:
dprintf3(( "MCI_SYSINFO command" ));
/*
** If we are returning a number forget about UNICODE,
** applies when (dwParam1 & MCI_SYSINFO_QUANTITY) is TRUE.
*/
if ( dwParam1 & MCI_SYSINFO_QUANTITY ) {
return mciSendCommandW( wDeviceID, wMessage, dwParam1, dwParam2 );
}
else {
LPMCI_SYSINFO_PARMSW lpInfoP = (LPMCI_SYSINFO_PARMSW)dwParam2;
DWORD len = BYTE_GIVEN_CHAR( lpInfoP->dwRetSize );
/*
** First save the original ascii string pointers.
*/
lpStr1 = (LPSTR)lpInfoP->lpstrReturn;
/*
** If there is somewhere to store the result then we
** must allocate temporary space (for Unicode result)
** and on return from mciSendCommandW translate the
** string to Ascii.
*/
if (len) {
if ( lpStr1 ) {
lpInfoP->lpstrReturn = mciAlloc( len );
if ( lpInfoP->lpstrReturn == NULL ) {
lpInfoP->lpstrReturn = (LPWSTR)lpStr1;
return MCIERR_OUT_OF_MEMORY;
}
lpStr2 = mciAlloc( len );
if ( lpStr2 == NULL ) {
mciFree( (LPWSTR)lpInfoP->lpstrReturn );
lpInfoP->lpstrReturn = (LPWSTR)lpStr1;
return MCIERR_OUT_OF_MEMORY;
}
}
} else {
/*
** Should we ZERO the string pointers in the parameter block?
** Yes, belts and braces !!
*/
lpInfoP->lpstrReturn = NULL;
}
/*
** Now call the unicode version
*/
dwRet = mciSendCommandW( wDeviceID, wMessage, dwParam1, dwParam2 );
/*
** Copy the unicode return string into ascii, if the
** user provided a return string
*/
if (len && lpStr1) {
if ((MMSYSERR_NOERROR == dwRet) && len) {
UnicodeStrToAsciiStr( (PBYTE)lpStr2,
(PBYTE)lpStr2 + len,
lpInfoP->lpstrReturn );
/* On return from mciSendCommandW lpInfoP->dwRetSize is
** equal to the number of characters copied to
** lpInfoP->lpstrReturn less the NULL terminator.
** So add one to lpInfoP->dwRetSize to include the NULL
** in the strncpy below.
**
** But ONLY if the original buffer was large enough.
*/
#ifdef DBCS
//fix kksuzuka: #3642
//have to copy byte length into ASCII buffer..
strncpy( (LPSTR)lpStr1, lpStr2,
min(BYTE_GIVEN_CHAR(lpInfoP->dwRetSize+1), CHAR_GIVEN_BYTE(len)));
#else
strncpy( (LPSTR)lpStr1, lpStr2,
min((UINT)lpInfoP->dwRetSize + 1, CHAR_GIVEN_BYTE(len)) );
#endif
#if DBG
dprintf3(( "Return param (UNICODE)= %ls", lpInfoP->lpstrReturn ));
dprintf3(( "Return param (ASCII) = %s", lpStr1 ));
#endif
}
/*
** Free temp storage and restore the original strings
*/
mciFree( lpInfoP->lpstrReturn );
lpInfoP->lpstrReturn = (LPWSTR)lpStr1;
mciFree( lpStr2 );
}
return dwRet;
}
default:
{
/*
** NewParms is allocated off the stack in order to minimize
** the number of calls to mciAlloc, and it means we do not
** have to remember to free it.
*/
DWORD NewParms[MCI_MAX_PARAM_SLOTS];
/*
** dwStrMask is used to store a bitmap representation of which
** offsets into dwParam2 contain strings. ie. bit 4 set
** means that dwParam2[4] is a string.
*/
DWORD dwStrMask = 0L;
/*
** fStrReturn is used as a reminder of whether a string return
** is expected or not. If the return type is not a string
** we just copy the bytes back as is. uReturnLength is the
** number of bytes to copy back. dwParm2 is used to ease some
** of the addressing used to access the dwParam2 array.
*/
BOOL fStrReturn = FALSE;
UINT uReturnLength = 0;
LPDWORD dwParm2 = (LPDWORD)dwParam2;
/*
** The remaining variables are used as we scan our way thru the
** command table.
*/
LPWSTR lpCommand, lpFirstParameter;
LPSTR lpReturnStrTemp;
UINT wID;
DWORD dwValue;
UINT wOffset32, wOffset1stParm32, uTable, uStrlenBytes;
PDWORD pdwParm32;
DWORD dwMask = 1;
if (!dwParam2) {
return mciSendCommandW( wDeviceID, wMessage, dwParam1, dwParam2);
}
/*
** Find the command table for the given command ID.
** If the command table is not there we have probably been
** given a duff device ID. Anyway exit with an internal
** error.
*/
lpCommand = FindCommandItem( wDeviceID, NULL, (LPWSTR)wMessage,
NULL, &uTable );
if ( lpCommand == NULL ) {
return MCIERR_UNSUPPORTED_FUNCTION;
}
#if DBG
ZeroMemory(NewParms, sizeof(NewParms));
#endif
/*
** Copy callback field.
*/
if ( dwParam1 & MCI_NOTIFY ) {
NewParms[0] = dwParm2[0];
}
/*
** Skip past command entry
*/
lpCommand = (LPWSTR)((LPBYTE)lpCommand +
mciEatCommandEntry( lpCommand, NULL, NULL ));
/*
** Get and remember the first parameter
*/
lpFirstParameter = lpCommand;
/*
** Skip past the DWORD callback field
*/
wOffset1stParm32 = 4;
lpCommand = (LPWSTR)((LPBYTE)lpCommand +
mciEatCommandEntry( lpCommand, &dwValue, &wID ));
/*
** If the first parameter is a return value, we have some
** special processing
*/
if ( wID == MCI_RETURN ) {
/*
** String return types are a special case.
*/
if ( dwValue == MCI_STRING ) {
dprintf3(( "Found a return string" ));
/*
** Get unicode string length in bytes and allocate
** some storage, but only if a valid length has been
** given. Otherwise set this field to NULL, we must
** use 0 here otherwise the MIPS compiler goes
** ape Xxxx. We set a flag to remind us to unthunk
** the return string later.
**
** Note that we are actually allocating lots of equally
** sized storage here. This saves on the number of times
** that we call mciAlloc.
*/
if ( uStrlenBytes = BYTE_GIVEN_CHAR( dwParm2[2] ) ) {
NewParms[1] = (DWORD)mciAlloc( uStrlenBytes * 2 );
dprintf4(( "Allocated %d bytes for the return string at %x", uStrlenBytes, NewParms[1] ));
if ( NewParms[1] == 0 ) {
mciUnlockCommandTable( uTable );
return MCIERR_OUT_OF_MEMORY;
}
lpReturnStrTemp = (LPSTR)(NewParms[1] + uStrlenBytes);
fStrReturn = TRUE;
}
else {
NewParms[1] = (DWORD)0;
}
/*
** Copy string length.
*/
NewParms[2] = dwParm2[2];
}
/*
** Adjust the offset of the first parameter.
*/
uReturnLength = mciGetParamSize( dwValue, wID );
wOffset1stParm32 += uReturnLength;
/*
** Save the new first parameter pointer
*/
lpFirstParameter = lpCommand;
}
/*
** Walk through each flag
*/
while ( dwMask != 0 ) {
/*
** Is this bit set?
*/
if ( (dwParam1 & dwMask) != 0 ) {
wOffset32 = wOffset1stParm32;
lpCommand = (LPWSTR)((LPBYTE)lpFirstParameter +
mciEatCommandEntry( lpFirstParameter,
&dwValue, &wID ));
/*
** What parameter uses this bit?
*/
while ( wID != MCI_END_COMMAND && dwValue != dwMask ) {
wOffset32 += mciGetParamSize( dwValue, wID );
if ( wID == MCI_CONSTANT ) {
while ( wID != MCI_END_CONSTANT ) {
lpCommand = (LPWSTR)((LPBYTE)lpCommand +
mciEatCommandEntry( lpCommand,
NULL, &wID ));
}
}
lpCommand = (LPWSTR)((LPBYTE)lpCommand +
mciEatCommandEntry( lpCommand,
&dwValue, &wID ));
}
if ( wID != MCI_END_COMMAND ) {
pdwParm32 = (LPDWORD)((LPBYTE)NewParms + wOffset32);
if ( wID == MCI_STRING ) {
/*
** Allocate a unicode string for this parameter
** and set the flag.
*/
*pdwParm32 = (DWORD)AllocUnicodeStr(
(LPSTR)*(LPDWORD)((LPBYTE)dwParm2 +
wOffset32) );
//
// Turn wOffset32 into a bit mask.
// wOffset32 is the slot number offset in bytes
dwStrMask |= 1 << ((wOffset32 >> 2) - 1);
// Calculate the slot position (offset / 4)
// decrement to get the number of bits to shift
// shift 1 that number of bits left
// and OR into the existing dwStrMask.
#if DBG
dprintf3(( "String at %x (Addr %x) (UNICODE)= %ls", wOffset32/4, *pdwParm32 , *pdwParm32 ));
dprintf3(( "String at %x (Addr %x) (ASCII) = %s", wOffset32/4, *pdwParm32 , (LPSTR)*(LPDWORD)((LPBYTE)dwParm2 + wOffset32) ));
#endif
}
else { // not a string
/*
** Otherwise copy the parameter as is, if
** there is anything to copy...
*/
wID = mciGetParamSize( dwValue, wID);
switch (wID) {
case 4:
*pdwParm32 = *(LPDWORD)((LPBYTE)dwParm2 + wOffset32);
break;
case 0:
break;
default:
// This will be sizeof(MCI_RECT) as of today (Jan 93)
CopyMemory(pdwParm32, (LPBYTE)dwParm2 + wOffset32, wID);
}
}
}
}
/*
** Go to the next flag
*/
dwMask <<= 1;
}
// If no strings needed converting. Use the original parameter block
if ( !(dwStrMask | fStrReturn)) {
// No strings in parameters. Use original parameter pointer
dprintf3(( "NO strings for command %4X", wMessage ));
dwRet = mciSendCommandW( wDeviceID, wMessage, dwParam1, dwParam2);
uReturnLength = 0; // We will not need to copy anything back
} else {
dprintf3(( "The unicode string mask is %8X fStrReturn %x", dwStrMask, fStrReturn ));
dwRet = mciSendCommandW( wDeviceID, wMessage, dwParam1, (DWORD)NewParms );
}
/*
** If there is a string return field we unthunk it here.
*/
if ( fStrReturn && uStrlenBytes ) {
/*
** If mciSendCommand worked then we need to convert the
** return string from unicode to ascii.
*/
if ( MMSYSERR_NOERROR == dwRet ) {
UnicodeStrToAsciiStr( (PBYTE)lpReturnStrTemp,
(PBYTE)lpReturnStrTemp + uStrlenBytes,
(LPWSTR)NewParms[1] );
/*
** Copy back the return string size.
*/
dwParm2[2] = NewParms[2];
/* On return from mciSendCommandW the dwRetSize field is
** equal to the number of characters copied to
** lpInfoP->lpstrReturn less the NULL terminator.
** So add one to lpInfoP->dwRetSize to include the NULL in
** the strncpy below.
**
** But ONLY if the original buffer was large enough.
*/
#ifdef DBCS
//fix kksuzuka: #3642
//have to copy byte length into ASCII buffer..
strncpy( (LPSTR)dwParm2[1], lpReturnStrTemp,
min( BYTE_GIVEN_CHAR(NewParms[2]+1),
CHAR_GIVEN_BYTE(uStrlenBytes)) );
#else
strncpy( (LPSTR)dwParm2[1], lpReturnStrTemp,
min( (UINT)NewParms[2] + 1,
CHAR_GIVEN_BYTE(uStrlenBytes)) );
#endif
#if DBG
dprintf3(( "Returned string (UNICODE)= %ls", NewParms[1] ));
dprintf3(( "Returned string (ASCII) = %s", dwParm2[1] ));
#endif
}
/*
** We need to free the string storage whether mciSendCommand
** worked or not.
*/
dprintf4(( "Freeing returned string at %x", NewParms[1] ));
mciFree( NewParms[1] );
}
/*
** Else if there is any other sort of return field unthunk
** it by copying across the bytes as is.
*/
else if ( uReturnLength ) {
dprintf3(( "Copying back %d returned bytes", uReturnLength ));
CopyMemory( (LPDWORD)dwParam2 + 1, NewParms + 1, uReturnLength );
}
/*
** Now go through the dwStrMask and free each field as indicated
** by the set bits in the mask. We start at 1 because the
** zero'th field is known to be a window handle.
*/
wOffset32 = 1;
for ( ; dwStrMask != 0; dwStrMask >>= 1, wOffset32++ ) {
if ( dwStrMask & 1 ) {
/*
** There is a string at NewParms[ wOffset32 ]
*/
dprintf3(( "Freeing string at %d (%x) (UNICODE) = %ls", wOffset32, NewParms[ wOffset32 ], (LPWSTR)NewParms[ wOffset32 ] ));
FreeUnicodeStr( (LPWSTR)NewParms[ wOffset32 ] );
}
}
dprintf4(( "Unlocking command table" ));
mciUnlockCommandTable( uTable );
}
}
return dwRet;
}
DWORD mciSendCommandW(
MCIDEVICEID wDeviceID,
UINT wMessage,
DWORD dwParam1,
DWORD dwParam2)
{
UINT wRet;
DWORD dwErr;
MCI_INTERNAL_OPEN_INFO OpenInfo;
// Initialize the device list
if (!MCI_bDeviceListInitialized && !mciInitDeviceList())
return MCIERR_OUT_OF_MEMORY;
dprintf3(("mciSendCommand, command=%x Device=%x",wMessage, wDeviceID));
//
// Send the command. This shell is responsible for adding the device ID
// to the error code if necessary
//
OpenInfo.hCallingTask = GetCurrentTask();
OpenInfo.lpstrParams = NULL;
OpenInfo.lpstrPointerList = NULL;
OpenInfo.wParsingError = 0;
dwErr = mciSendCommandInternal( wDeviceID, wMessage,
dwParam1, dwParam2, &OpenInfo);
wRet = LOWORD(dwErr);
dprintf4(("Return value from mciSendCommandInternal %x", wRet));
// If the return value contains a resource ID then clear it from the high word
if (dwErr & MCI_RESOURCE_RETURNED) {
((LPDWORD)dwParam2)[1] &= 0xFFFF;
}
// If the error message is in a driver, store the driver ID in the high
// word of the error code
if (wRet >= MCIERR_CUSTOM_DRIVER_BASE) {
dwErr = (DWORD)wRet | ((DWORD)wDeviceID << 16);
} else {
dwErr = (DWORD)wRet;
}
#if DBG
// Dump the error text if any to the debug terminal
// Note that dwErr != 0 is a VALID return for driver messages. Only
// trap MCI messages
if ((dwErr != 0) && (wMessage>=MCI_FIRST))
{
WCHAR strTemp[MAXERRORLENGTH];
if (!mciGetErrorStringW( dwErr,
strTemp,
MAXERRORLENGTH ) ) {
LoadStringW( ghInst, STR_MCISCERRTXT, strTemp,
MAXERRORLENGTH );
}
dprintf1(("mciSendCommand: %ls", strTemp));
}
#endif
//
// Somehow since 3.51 the priorities of threads in WOW have
// changed and now the application thread is running at a
// higher priority than that of regular threads (i.e. mciavi's
// worker thread). Many applications that use MCI tend to
// poll the status of the MCI device that is playing. This
// polling is causing the other threads in WOW to be starved
// and brings the playback of AVIs to a crawl. This sleep
// will keep the application thread from buring so much of
// the CPU and allow other threads, for example MCIAVI, to
// do it's work.
//
if ( WinmmRunningInWOW )
{
Sleep(0);
}
return dwErr;
}
//***************************************************************************
// mciColonizeDigit
//
// Grab colonized digit
// Return is number of bytes written to output (NOT including NULL)
// or 0 if out of room in output buffer (but is terminated anyway)
// If there is room then at least two digits are written, padded with '0'
// if necessary. The function assumes that the buffer size is non-zero length,
// as this is checked in the function that calls the function that calls us.
//
//***************************************************************************
STATICFN UINT NEAR mciColonizeDigit(
LPWSTR lpstrOutput,
CHAR cDigit,
UINT uSize)
{
UINT uCount = 0;
#if DBG
// There is room for terminating NULL
if (uSize == 0) {
dprintf(("MCI: Internal error!!"));
return 0;
}
#endif
uCount = 2;
// If there is room for at least two digits
if (uSize >= 3)
{
if (cDigit >= 100)
{
uCount = 3;
if (uSize < 4)
goto terminate;
*lpstrOutput++ = (WCHAR)((cDigit / 100) % 10 + '0');
cDigit = (CHAR)(cDigit % 100);
}
*lpstrOutput++ = (WCHAR)(cDigit / 10 + '0');
*lpstrOutput++ = (WCHAR)(cDigit % 10 + '0');
}
terminate:;
*lpstrOutput++ = '\0';
// If we ran out of room then return an error
return (uCount >= uSize) ? 0 : uCount;
}
/*
* @doc INTERNAL MCI
* @func BOOL | mciColonize | Convert a colonized dword into a string
* representation
*
* @parm LPWSTR | lpstrOutput | Output buffer
*
* @parm UINT | uLength | Size of output buffer
*
* @parm DWORD | dwData | Value to convert
*
* @parm UINT | uType | Either MCI_COLONIZED3_RETURN or
* MCI_COLONIZED4_RETURN is set (HIWORD portion only!)
*
* @comm Example: For C4, 0x01020304 is converted to "04:03:02:01"
* For C3, 0x01020304 is converted to "04:03:02"
*
* @rdesc FALSE if there is not enough room in the output buffer
*
*/
STATICFN BOOL NEAR mciColonize(
LPWSTR lpstrOutput,
UINT uLength,
DWORD dwData,
UINT uType)
{
LPSTR lpstrInput = (LPSTR)&dwData; // For stepping over each byte of input
UINT uSize;
int i;
for (i = 1; i <= (uType & HIWORD(MCI_COLONIZED3_RETURN) ? 3 : 4); ++i)
{
uSize = mciColonizeDigit( lpstrOutput, *lpstrInput++, uLength);
if (uSize == 0)
return FALSE;
lpstrOutput += uSize;
uLength -= uSize;
if (i < 3 || i < 4 && uType & HIWORD(MCI_COLONIZED4_RETURN))
{
--uLength;
if (uLength == 0)
return FALSE;
else
*lpstrOutput++ = ':';
}
}
return TRUE;
}
//***********************************************************************
// mciConvertReturnValue
//
// Convert the return value to a return string
//
//***********************************************************************
UINT mciConvertReturnValue(
UINT uType,
UINT uErrCode,
MCIDEVICEID wDeviceID,
LPDWORD dwParams,
LPWSTR lpstrReturnString,
UINT uReturnLength ) // This is a character length
{
UINT wExternalTable;
if (lpstrReturnString == NULL || uReturnLength == 0)
return 0;
switch (uType)
{
case MCI_INTEGER:
case MCI_HWND:
case MCI_HPAL:
case MCI_HDC:
// Convert integer or resource return value to string
if (uErrCode & HIWORD(MCI_RESOURCE_RETURNED))
{
int nResId = HIWORD(dwParams[1]);
LPMCI_DEVICE_NODE nodeWorking;
HANDLE hInstance;
mciEnter("mciConvertReturnValue");
nodeWorking = MCI_lpDeviceList[wDeviceID];
mciLeave("mciConvertReturnValue");
if (nodeWorking == NULL)
{
// Return blank string on memory error
dprintf1(("mciConvertReturnValue Warning:NULL device node"));
break;
}
// Return value is a resource
if (uErrCode & HIWORD(MCI_RESOURCE_DRIVER))
{
// Return string ID belongs to driver
hInstance = nodeWorking->hDriver;
// WAS hInstance = nodeWorking->hCreatorTask;
wExternalTable = nodeWorking->wCustomCommandTable;
} else
{
wExternalTable = nodeWorking->wCommandTable;
hInstance = ghInst;
}
// Try to get string from custom or device specific external table
if ( wExternalTable == MCI_TABLE_NOT_PRESENT ||
command_tables[wExternalTable].hModule == NULL ||
LoadStringW( command_tables[wExternalTable].hModule,
nResId,
lpstrReturnString,
uReturnLength ) == 0 )
{
// Try to get string from CORE.MCI if it's not from the driver
if (hInstance != ghInst ||
command_tables[0].hModule == NULL ||
LoadStringW( command_tables[0].hModule, nResId,
lpstrReturnString,
uReturnLength ) == 0) {
// Get string from custom module or WINMM.DLL
LoadStringW( hInstance, nResId, lpstrReturnString,
uReturnLength);
}
}
} else if (uErrCode & HIWORD(MCI_COLONIZED3_RETURN) ||
uErrCode & HIWORD(MCI_COLONIZED4_RETURN))
{
if (!mciColonize (lpstrReturnString,
uReturnLength, dwParams[1], uErrCode))
return MCIERR_PARAM_OVERFLOW;
} else
// Convert integer return value to string
// NEED BETTER ERROR CHECKING !!LATER!!
// MUST FIND A VERSION OF THIS WHICH WON'T OVERFLOW OUTPUT BUFFER
{
DWORD dwTemp;
// Need room for a sign, up to ten digits and a NULL
if (uReturnLength < 12)
return MCIERR_PARAM_OVERFLOW;
if (uType == MCI_STRING ||
uErrCode == HIWORD(MCI_INTEGER_RETURNED))
dwTemp = *(LPDWORD)dwParams[1];
else
dwTemp = dwParams[1];
wsprintfW(lpstrReturnString, szLongFormat, dwTemp);
}
break;
case MCI_RECT:
// Need from for 4 times (a sign plus 5 digits) plus three spaces and a NULL
if (uReturnLength < 4 * 6 + 4)
return MCIERR_PARAM_OVERFLOW;
wsprintfW (lpstrReturnString, szRectFormat,
((PMCI_ANIM_RECT_PARMS)dwParams)->rc.left,
((PMCI_ANIM_RECT_PARMS)dwParams)->rc.top,
((PMCI_ANIM_RECT_PARMS)dwParams)->rc.right,
((PMCI_ANIM_RECT_PARMS)dwParams)->rc.bottom);
break;
default:
// Only support INTEGERs & MIXED
dprintf1(("mciConvertReturnValue Warning: Unknown return type"));
return MCIERR_PARSER_INTERNAL;
}
return 0;
}
//***********************************************************************
// mciSeparateCommandParts
//
// Pull off the command name and device name from the command string,
// leaving *lplpstrCommand pointing past the device name
//
// Returns 0 or an error code on failure. If successful, the caller must
// free the pstrCommandName and pstrDeviceName
//
// If bCompound then check for a '!' separator in the extracted device name
// and return only the element part. This is done so that inter-task
// commands to auto-opened devices will include the correct device name
//
//***********************************************************************
STATICFN DWORD NEAR mciSeparateCommandParts(
LPCWSTR FAR *lplpstrCommand,
BOOL bCompound,
LPWSTR FAR *lplpstrCommandName,
LPWSTR FAR *lplpstrDeviceName)
{
LPWSTR lpstrCommand;
UINT uErr;
// Localize the input
lpstrCommand = (LPWSTR)*lplpstrCommand;
// Remove leading spaces
while (*lpstrCommand == ' ') {
++lpstrCommand;
}
if (*lpstrCommand == '\0') {
return MCIERR_MISSING_COMMAND_STRING;
}
// Pull the command name off the front of the command string
if ((uErr = mciEatToken ( (LPCWSTR *)&lpstrCommand, ' ', lplpstrCommandName, FALSE))
!= 0) {
return uErr;
}
// Skip past spaces
while (*lpstrCommand == ' ') {
++lpstrCommand;
}
// If we're looking for compound elements then yank off any leading
// device type if it is not the open command
if (bCompound && lstrcmpiW( wszOpen, *lplpstrCommandName) != 0)
{
LPWSTR lpstrTemp = lpstrCommand;
while (*lpstrTemp != '\0')
{
if (*lpstrTemp == '!')
{
// A ! was found so skip past it
lpstrCommand = lpstrTemp + 1;
break;
} else
++lpstrTemp;
}
}
// Pull the device name off of the command string
if ((uErr = mciEatToken( (LPCWSTR *)&lpstrCommand, ' ', lplpstrDeviceName, FALSE))
!= 0)
{
mciFree (*lplpstrCommandName);
return uErr;
}
// Fix up the results
*lplpstrCommand = lpstrCommand;
return 0;
}
/*--------------------------------------------------------------------*\
* mciSendSystemString
*
\*--------------------------------------------------------------------*/
STATICFN DWORD mciSendSystemString(
LPCWSTR lpstrCommand,
DWORD dwAdditionalFlags,
LPWSTR lpstrReturnString,
UINT uReturnLength)
{
DWORD dwRet;
LPMCI_SYSTEM_MESSAGE lpMessage;
DWORD CurDirSize;
dprintf2(("\nmciSendSystemString(%ls)", lpstrCommand));
if (!CreatehwndNotify()) {
dprintf1(("NULL notification window handle"));
return MCIERR_INTERNAL;
}
// Get a buffer to hold the current path PLUS an MCI_SYSTEM_MESSAGE structure
CurDirSize = GetCurrentDirectoryW( 0, NULL ); // Get size required.
// Remember the NULL is not included
if ( !CurDirSize ) { // Add 1 for the terminator
dprintf1(("NULL current path"));
return MCIERR_GET_CD;
}
CurDirSize++;
if (NULL != (lpMessage = mciAlloc( sizeof(MCI_SYSTEM_MESSAGE)
+ BYTE_GIVEN_CHAR( CurDirSize ) ))) {
LPWSTR lpstrPath = (LPWSTR)( (LPBYTE)lpMessage
+ sizeof( MCI_SYSTEM_MESSAGE ) );
if ( GetCurrentDirectoryW( CurDirSize, lpstrPath ) ) {
lpMessage->lpstrCommand = (LPWSTR)lpstrCommand;
lpMessage->dwAdditionalFlags = dwAdditionalFlags;
lpMessage->lpstrReturnString = lpstrReturnString;
lpMessage->uReturnLength = uReturnLength;
#if DBG
if ((0 == uReturnLength) && (0 != lpstrReturnString)) {
dprintf1((" ******** Return length 0, non 0 return address"));
}
#endif
lpMessage->hCallingTask = GetCurrentTask();
lpMessage->lpstrNewDirectory = lpstrPath;
// BUGBUG This is where we need to do some thread stuff
dwRet = (DWORD)SendMessage(hwndNotify, MM_MCISYSTEM_STRING, 0, (LONG)lpMessage);
//dwRet = mciSendStringInternal (NULL, NULL, 0, NULL, lpMessage);
} else {
dprintf1(("mciSendSystemString: cannot get current directory\n"));
dwRet = MCIERR_GET_CD;
}
mciFree(lpMessage);
} else {
dprintf1(("mciSendSystemString: cannot allocate message block\n"));
dwRet = MCIERR_OUT_OF_MEMORY;
}
return dwRet;
}
/*--------------------------------------------------------------------*\
* mciRelaySystemString
*
* Internal:
*
\*--------------------------------------------------------------------*/
DWORD mciRelaySystemString(
LPMCI_SYSTEM_MESSAGE lpMessage)
{
DWORD dwRet;
LPWSTR lpstrOldPath;
DWORD CurDirSize;
lpstrOldPath = 0; // Initialise to remove warning message
#if DBG
dprintf2(("mciRelaySystemString(%ls)", lpMessage->lpstrCommand));
#endif
// Get a buffer to hold the current path
CurDirSize = GetCurrentDirectoryW(0, lpstrOldPath); // Get size required.
// Remember the NULL is not included
if (!CurDirSize) { // Add 1 for the terminator AFTER testing
dprintf1(("NULL current path")); // for 0 from GetCurrentDirectory
return MCIERR_INTERNAL;
}
CurDirSize++;
/*
* Allocate space to hold the current path
* Fill the allocated space with the current path
* Set the new current directory to that in the message
* Execute the MCI command via SentStringInternal
* Reset to old current directory
* BUGBUG: This code is not reentrant on the same PROCESS
*/
if (NULL != (lpstrOldPath = mciAlloc( BYTE_GIVEN_CHAR(CurDirSize) ))) {
if (GetCurrentDirectoryW(CurDirSize, lpstrOldPath)) {
if (SetCurrentDirectoryW(lpMessage->lpstrNewDirectory)) {
dwRet = mciSendStringInternal (NULL, NULL, 0, NULL, lpMessage);
if (!SetCurrentDirectoryW(lpstrOldPath)) {
dprintf1(("mciRelaySystemString: WARNING, cannot restore path\n"));
}
} else {
dprintf1(("mciRelaySystemString: cannot set new path\n"));
dwRet = MCIERR_SET_CD;
}
} else {
dprintf1(("mciRelaySystemString: cannot get old path\n"));
dwRet = MCIERR_GET_CD;
}
mciFree(lpstrOldPath);
} else {
dprintf1(("mciRelaySystemString: cannot allocate old path\n"));
dwRet = MCIERR_OUT_OF_MEMORY;
}
return dwRet;
}
//***********************************************************************
// mciFindNotify
//
// Returns TRUE if "notify" is contained in string with leading blank
// and trailing blank or '\0'
//***********************************************************************
STATICFN BOOL mciFindNotify(
LPWSTR lpString)
{
while (*lpString != '\0')
{
// "notify" must be preceded by a blank
if (*lpString++ == ' ')
{
LPWSTR lpTemp;
lpTemp = wszNotify;
while (*lpTemp != '\0' && *lpString != '\0' &&
*lpTemp == MCI_TOLOWER(*lpString))
{
++lpTemp;
++lpString;
}
// "notify" must be followed by a blank or a null
if (*lpTemp == '\0' && // implies that wszNotify was found
(*lpString == '\0' || *lpString == ' '))
return TRUE;
}
}
return FALSE;
}
/*
* @doc INTERNAL MCI
*
* @func UINT | mciAutoOpenDevice | Try to auto-open the given device and
* then send the given command with notification sent to the system task
* window proc which sends a close command to the device on receipt
*
* @parm LPWSTR | lpstrDeviceName | The device name to open
*
* @parm LPWSTR | lpstrCommand | The full command to send including the
* device name which must be the same as lpstrDeviceName
*
* @parm LPWSTR | lpstrReturnString | The caller's return string buffer
*
* @parm UINT | uReturnLength | Size of the caller's return string buffer
*
* @rdesc The errorcode to return to the user
*/
STATICFN UINT NEAR mciAutoOpenDevice(
LPWSTR lpstrDeviceName,
LPWSTR lpstrCommand,
LPWSTR lpstrReturnString,
UINT uReturnLength)
{
LPWSTR lpstrTempCommand, lpstrTempReturn = NULL;
UINT uErr;
dprintf2(("mciAutoOpenDevice(%ls, %ls)", lpstrDeviceName, lpstrCommand));
//
// Don't allow recursive auto opens on the mciWindow thread!
// This can happen when the device auto closes between a command (eg
// status) being issued on the client thread and executed on the
// mciWindow thread.
//
// mciSendStringW will detect this return code and try again - probably
// causing the device to be auto-opened on the caller's thread.
//
if ((DWORD)GetCurrentTask() == mciWindowThreadId) {
return MCIERR_AUTO_ALREADY_CLOSED;
}
// "notify" not allowed. This will be found by the parser but the wrong
// error message will be returned.
if (mciFindNotify (lpstrCommand)) {
return MCIERR_NOTIFY_ON_AUTO_OPEN;
}
// Build the command string "open <device name>"
// Must be GMEM_SHARE for system task
// "open" + blank + device name + NULL
if ( (lpstrTempCommand = mciAlloc(
BYTE_GIVEN_CHAR( wcslen(lpstrDeviceName) +
/* Sizeof(wszOpen) == OPEN+NULL */
sizeof( wszOpen ) +
sizeof( WCHAR ) ) ) ) == NULL) {
return MCIERR_OUT_OF_MEMORY;
}
#ifdef WHICH_IS_BEST
wcscpy (lpstrTempCommand, wszOpen);
wcscat (lpstrTempCommand, L" ");
wcscat (lpstrTempCommand, lpstrDeviceName);
#else
wsprintfW(lpstrTempCommand, szCmdFormat, wszOpen, lpstrDeviceName);
#endif
// Get the open string into the system task via a SendMessage() to mmWndProc
uErr = (UINT)mciSendSystemString (lpstrTempCommand, 0L, NULL, 0);
mciFree (lpstrTempCommand);
if (uErr != 0) {
return uErr;
}
lpstrTempCommand = NULL;
// Must make a GMEM_SHARE copy of the return string for system task
if ( lpstrReturnString != NULL ) {
if ((lpstrTempReturn = mciAlloc(
BYTE_GIVEN_CHAR(uReturnLength + 1) )) == NULL )
{
// Close the device
mciDriverNotify (hwndNotify, mciGetDeviceIDW( lpstrDeviceName), 0);
return MCIERR_OUT_OF_MEMORY;
}
#if DBG
*lpstrTempReturn = 0;
#endif
}
// Get the user command string into the system task via a SendMessage()
// to mmWndProc
// The notification handle is also mmWndProc
uErr = (UINT)mciSendSystemString( lpstrCommand, MCI_NOTIFY, lpstrTempReturn,
uReturnLength);
// Copy the return string into the user's buffer
if (lpstrReturnString != NULL) {
if (uErr == 0) {
wcscpy( lpstrReturnString, lpstrTempReturn);
} else { // ERROR and no string to be copied
WinAssert(!*lpstrTempReturn);
}
mciFree( lpstrTempReturn);
}
// If there was an error we must close the device
if (uErr != 0)
{
mciAutoCloseDevice( lpstrDeviceName);
}
return uErr;
}
//*************************************************************************
// mciSendStringInternal
//
// Identical to mciSendString() but the lpMessage parameter is tacked on
//
// lpMessage comes from inter-task mciSendString and includes an
// hCallingTask item which is sent down the the OPEN command
//
//*************************************************************************
STATICFN DWORD mciSendStringInternal(
LPCWSTR lpstrCommand,
LPWSTR lpstrReturnString,
UINT uReturnLength, // This is a character length - NOT bytes
HANDLE hCallback,
LPMCI_SYSTEM_MESSAGE lpMessage)
{
UINT wID;
UINT uLen;
UINT uErr = 0;
UINT uConvertReturnValue;
UINT wMessage;
MCIDEVICEID wDeviceID;
LPDWORD lpdwParams = NULL;
DWORD dwReturn, dwFlags = 0, dwAdditionalFlags = 0;
LPWSTR lpCommandItem;
DWORD dwErr = 0, dwRetType;
UINT wTable = (UINT)MCI_TABLE_NOT_PRESENT;
LPWSTR lpstrDeviceName = NULL;
LPWSTR lpstrCommandName = NULL;
LPWSTR FAR *lpstrPointerList = NULL;
LPWSTR lpstrCommandStart;
HANDLE hCallingTask;
UINT wParsingError;
BOOL bNewDevice;
LPWSTR lpstrInputCopy = NULL;
// Did this call come in from another task
if (lpMessage != NULL)
{
dprintf3(("mciSendStringInternal: remote task call"));
// Yes so restore info
lpstrCommand = lpMessage->lpstrCommand;
dwAdditionalFlags = lpMessage->dwAdditionalFlags;
lpstrReturnString = lpMessage->lpstrReturnString;
uReturnLength = lpMessage->uReturnLength;
#if DBG
if ((0 == uReturnLength) && (0 != lpstrReturnString)) {
dprintf((" -------- Return length 0, non 0 return address"));
}
#endif
hCallback = hwndNotify;
hCallingTask = lpMessage->hCallingTask;
lpstrInputCopy = NULL;
} else
{
BOOL bInQuotes = FALSE;
// No, so set hCallingTask to current thread
hCallingTask = GetCurrentTask();
if (lpstrCommand == NULL) {
return MCIERR_MISSING_COMMAND_STRING;
}
dprintf2(("mciSendString command ->%ls<-",lpstrCommand));
// Make a copy of the input string and convert tabs to spaces except
// when inside a quoted string
if ( (lpstrInputCopy = mciAlloc(
BYTE_GIVEN_CHAR( wcslen(lpstrCommand) + 1 ) ) ) == NULL ) {
return MCIERR_OUT_OF_MEMORY;
}
wcscpy(lpstrInputCopy, lpstrCommand); // Copies to the allocated area
lpstrCommand = lpstrInputCopy; // Reset string pointer to copy
lpstrCommandStart = (LPWSTR)lpstrCommand;
while (*lpstrCommandStart != '\0')
{
if (*lpstrCommandStart == '"') {
bInQuotes = !bInQuotes;
}
else if (!bInQuotes && *lpstrCommandStart == '\t') {
*lpstrCommandStart = ' ';
}
++lpstrCommandStart;
}
}
lpstrCommandStart = (LPWSTR)lpstrCommand;
if (lpstrReturnString == NULL) {
// As an additional safeguard against writing into
// the output buffer when the return string pointer is NULL,
// set its length to 0
uReturnLength = 0;
} else {
#if DBG
if (0 == uReturnLength) {
dprintf(("Return length of zero, but now writing to return string"));
}
#endif
// Set return to empty string so that it won't print out garbage if not
// touched again
*lpstrReturnString = '\0';
}
// Pull the command name and device name off the command string
if ((dwReturn = mciSeparateCommandParts( (LPCWSTR FAR *)&lpstrCommand,
lpMessage != NULL,
&lpstrCommandName,
&lpstrDeviceName)) != 0)
goto exitfn;
// Get the device id (if any) of the given device name
wDeviceID = mciGetDeviceIDW(lpstrDeviceName);
// Allow "new" for an empty device name
if (wDeviceID == 0 && lstrcmpiW (lpstrDeviceName, wszNew) == 0)
{
bNewDevice = TRUE;
*lpstrDeviceName = '\0';
} else {
bNewDevice = FALSE;
}
// // If the call does not come from another task
// if (MCI_VALID_DEVICE_ID(wDeviceID) && hCallingTask == GetCurrentTask())
// {
// LPMCI_DEVICE_NODE nodeWorking = MCI_lpDeviceList[wDeviceID];
// if (nodeWorking == NULL)
// {
// uErr = MCIERR_INTERNAL;
// goto cleanup;
// }
// // Was the device opened by this task
// if (nodeWorking->hOpeningTask != nodeWorking->hCreatorTask)
// // No so send the string inter-task
// {
// mciFree(lpstrCommandName);
// mciFree(lpstrDeviceName);
// dwReturn = mciSendSystemString (lpstrCommandStart, lpstrReturnString,
// uReturnLength);
// goto exitfn;
// }
// }
// Look up the command name
wMessage = mciParseCommand( wDeviceID, lpstrCommandName, lpstrDeviceName,
&lpCommandItem, &wTable);
// If the device was auto-opened the request will go to the auto thread.
// We do not hang around to find out what happens. (The device could
// close at any time.)
mciEnter("mciSendStringInternal");
if (MCI_VALID_DEVICE_ID(wDeviceID))
{
LPMCI_DEVICE_NODE nodeWorking;
nodeWorking = MCI_lpDeviceList[wDeviceID];
// Is there a pending auto-close message?
if (ISAUTOCLOSING(nodeWorking))
{
uErr = MCIERR_DEVICE_LOCKED;
mciLeave("mciSendStringInternal");
goto cleanup;
// If the call does not come from another task and is not owned by this task
// and is not the SYSINFO command
} else if (lpMessage == NULL &&
nodeWorking->hOpeningTask != nodeWorking->hCreatorTask &&
wMessage != MCI_SYSINFO)
// Send the string inter-task
{
if ( mciFindNotify( lpstrCommandStart) )
{
uErr = MCIERR_NOTIFY_ON_AUTO_OPEN;
mciLeave("mciSendStringInternal");
goto cleanup;
}
else
{
LPWSTR lpstrReturnStringCopy;
mciFree(lpstrCommandName);
mciFree(lpstrDeviceName);
mciUnlockCommandTable (wTable);
if (uReturnLength) {
lpstrReturnStringCopy = mciAlloc (
BYTE_GIVEN_CHAR(uReturnLength + 1) );
} else {
lpstrReturnStringCopy = NULL;
}
mciLeave("mciSendStringInternal");
// If we failed to allocate a return string we return
// an error. Note: return strings are optional
if ((uReturnLength==0) || (lpstrReturnStringCopy != NULL) )
{
dwReturn = mciSendSystemString( lpstrCommandStart,
0L,
lpstrReturnStringCopy,
uReturnLength);
if (uReturnLength) {
wcscpy( lpstrReturnString, lpstrReturnStringCopy);
mciFree( lpstrReturnStringCopy);
}
} else {
dwReturn = MCIERR_OUT_OF_MEMORY;
}
goto exitfn;
}
} else {
mciLeave("mciSendStringInternal");
}
}
else {
mciLeave("mciSendStringInternal");
}
// There must be a device name (except for the MCI_SOUND message)
if (*lpstrDeviceName == '\0' && wMessage != MCI_SOUND && !bNewDevice)
{
uErr = MCIERR_MISSING_DEVICE_NAME;
goto cleanup;
}
// The command must appear in the parser tables
if (wMessage == 0)
{
uErr = MCIERR_UNRECOGNIZED_COMMAND;
goto cleanup;
}
// The "new" device name is only legal for the open message
if (bNewDevice)
{
if (wMessage != MCI_OPEN)
{
uErr = MCIERR_INVALID_DEVICE_NAME;
goto cleanup;
}
}
// If there was no device ID
if (wDeviceID == 0)
{
// If auto open is not legal (usually internal commands)
if (MCI_CANNOT_AUTO_OPEN (wMessage))
{
// If the command needs an open device
if (!MCI_DO_NOT_NEED_OPEN (wMessage))
{
dprintf1(("mciSendStringInternal: device needs open"));
uErr = MCIERR_INVALID_DEVICE_NAME;
goto cleanup;
}
} else {
// If auto open is legal try to open the device automatically
uErr = mciAutoOpenDevice( lpstrDeviceName, lpstrCommandStart,
lpstrReturnString, uReturnLength);
// wDeviceID = MCI_ALL_DEVICE_ID;
goto cleanup;
}
}
//
// Parse the command parameters
//
if ((lpdwParams = (LPDWORD)mciAlloc( sizeof(DWORD) * MCI_MAX_PARAM_SLOTS))
== NULL)
{
uErr = MCIERR_OUT_OF_MEMORY;
goto cleanup;
}
uErr = mciParseParams( wMessage, lpstrCommand, lpCommandItem, &dwFlags,
(LPWSTR)lpdwParams,
MCI_MAX_PARAM_SLOTS * sizeof(DWORD),
&lpstrPointerList, &wParsingError);
if (uErr != 0) {
goto cleanup;
}
// The 'new' device keyword requires an alias
if (bNewDevice && !(dwFlags & MCI_OPEN_ALIAS))
{
uErr = MCIERR_NEW_REQUIRES_ALIAS;
goto cleanup;
}
// Parsed OK so execute command
// Special processing for the MCI_OPEN message's parameters
if (wMessage == MCI_OPEN)
{
// Manually reference the device type and device element
if (dwFlags & MCI_OPEN_TYPE)
{
// The type name was specified explicitly as a parameter
// so the given device name is the element name
((LPMCI_OPEN_PARMSW)lpdwParams)->lpstrElementName = lpstrDeviceName;
dwFlags |= MCI_OPEN_ELEMENT;
} else
{
// A type must be explicitly specified when "new" is used
if (bNewDevice)
{
uErr = MCIERR_INVALID_DEVICE_NAME;
goto cleanup;
}
// The device type is the given device name. There is no element name
((LPMCI_OPEN_PARMSW)lpdwParams)->lpstrDeviceType = lpstrDeviceName;
((LPMCI_OPEN_PARMSW)lpdwParams)->lpstrElementName = NULL;
dwFlags |= MCI_OPEN_TYPE;
}
}
else if (wMessage == MCI_SOUND && *lpstrDeviceName != '\0')
{
// Kludge the sound name for SOUND
// mciToLower (lpstrDeviceName);
if (lstrcmpiW(lpstrDeviceName, wszNotify) == 0)
{
*lpstrDeviceName = '\0';
dwFlags |= MCI_NOTIFY;
}
else if ( lstrcmpiW( lpstrDeviceName, wszWait ) == 0)
{
*lpstrDeviceName = '\0';
dwFlags |= MCI_WAIT;
}
else
{
((LPMCI_SOUND_PARMSW)lpdwParams)->lpstrSoundName = lpstrDeviceName;
dwFlags |= MCI_SOUND_NAME;
}
}
// Figure out what kind of return value to expect
// Initialize flag
uConvertReturnValue = 0;
// Skip past header
uLen = mciEatCommandEntry (lpCommandItem, NULL, NULL);
// Get return value (if any)
mciEatCommandEntry ( (LPWSTR)((LPBYTE)lpCommandItem + uLen),
&dwRetType, &wID);
if (wID == MCI_RETURN)
{
// There is a return value
if (wDeviceID == MCI_ALL_DEVICE_ID && wMessage != MCI_SYSINFO)
{
uErr = MCIERR_CANNOT_USE_ALL;
goto cleanup;
}
switch (dwRetType)
{
case MCI_STRING:
// The return value is a string, point output buffer to user's buffer
lpdwParams[1] = (DWORD)lpstrReturnString;
lpdwParams[2] = (DWORD)uReturnLength;
break;
case MCI_INTEGER:
case MCI_HWND:
case MCI_HPAL:
case MCI_HDC:
// The return value is an integer, flag to convert it to a string later
// new uConvertReturnValue = MCI_INTEGER;
// new break;
case MCI_RECT:
// The return value is an rect, flag to convert it to a string later
// new uConvertReturnValue = MCI_RECT;
/* NEW */ uConvertReturnValue = (UINT)dwRetType;
break;
#if DBG
default:
dprintf1(("mciSendStringInternal: Unknown return type %d",dwRetType));
break;
#endif
}
}
// We don't need this around anymore
mciUnlockCommandTable (wTable);
wTable = (UINT)MCI_TABLE_NOT_PRESENT;
/* Fill the callback entry */
lpdwParams[0] = (DWORD)hCallback;
// Kludge the type number for SYSINFO
if (wMessage == MCI_SYSINFO) {
((LPMCI_SYSINFO_PARMS)lpdwParams)->wDeviceType =
mciLookUpType(lpstrDeviceName);
}
// Now we actually send the command further into the bowels of MCI!
// The INTERNAL version of mciSendCommand is used in order to get
// special return description information encoded in the high word
// of the return value and to get back the list of pointers allocated
// by any parsing done in the open command
{
MCI_INTERNAL_OPEN_INFO OpenInfo;
OpenInfo.lpstrParams = (LPWSTR)lpstrCommand;
OpenInfo.lpstrPointerList = lpstrPointerList;
OpenInfo.hCallingTask = hCallingTask;
OpenInfo.wParsingError = wParsingError;
dwErr = mciSendCommandInternal (wDeviceID, wMessage,
dwFlags | dwAdditionalFlags,
(DWORD)(LPDWORD)lpdwParams,
&OpenInfo);
// If the command was reparsed there may be a new pointer list
// and the old one was free'd
lpstrPointerList = OpenInfo.lpstrPointerList;
}
uErr = LOWORD(dwErr);
if (uErr != 0) {
// If command execution error
goto cleanup;
}
// Command executed OK
// See if a string return came back with an integer instead
if (dwErr & MCI_INTEGER_RETURNED) {
uConvertReturnValue = MCI_INTEGER;
}
// If the return value must be converted
if (uConvertReturnValue != 0 && uReturnLength != 0) {
uErr = mciConvertReturnValue( uConvertReturnValue, HIWORD(dwErr),
wDeviceID, lpdwParams,
lpstrReturnString, uReturnLength);
}
cleanup:;
if (wTable != MCI_TABLE_NOT_PRESENT) {
mciUnlockCommandTable (wTable);
}
mciFree(lpstrCommandName);
mciFree(lpstrDeviceName);
if (lpdwParams != NULL) {
mciFree (lpdwParams);
}
// Free any memory used by string parameters
mciParserFree (lpstrPointerList);
dwReturn = (uErr >= MCIERR_CUSTOM_DRIVER_BASE ?
(DWORD)uErr | (DWORD)wDeviceID << 16 :
(DWORD)uErr);
#if DBG
if (dwReturn != 0)
{
WCHAR strTemp[MAXERRORLENGTH];
if (!mciGetErrorStringW( dwReturn, strTemp,
sizeof(strTemp) / sizeof(WCHAR) ) ) {
LoadStringW( ghInst, STR_MCISSERRTXT, strTemp,
sizeof(strTemp) / sizeof(WCHAR) );
}
else {
dprintf1(( "mciSendString: %ls", strTemp ));
}
}
#endif
exitfn:
if (lpstrInputCopy != NULL) {
mciFree (lpstrInputCopy);
}
#if DBG
mciCheckLocks();
#endif
return dwReturn;
}
/*
* @doc EXTERNAL MCI
*
* @api DWORD | mciSendString | This function sends a command string to an
* MCI device. The device that the command is sent to is specified in the
* command string.
*
* @parm LPCTSTR | lpstrCommand | Points to an MCI command string of the form:
* [command] [device] [parameters].
*
* @parm LPTSTR | lpstrReturnString | Specifies a buffer for return
* information. If no return information is needed, you can specify
* NULL for this parameter.
*
* @parm UINT | uReturnLength | Specifies the size of the return buffer
* specified by <p lpstrReturnString>.
*
* @parm HANDLE | hCallback | Specifies a handle to a window to call back
* if "notify" was specified in the command string.
*
* @rdesc Returns zero if the function was successful. Otherwise, it returns
* error information. The low-order word
* of the returned DWORD contains the error return value.
*
* To get a textual description of <f mciSendString> return values,
* pass the return value to <f mciGetErrorString>.
*
* The error returns listed for <f mciSendCommand> also apply to
* <f mciSendString>. The following error returns are unique to
* <f mciSendString>:
*
* @flag MCIERR_BAD_CONSTANT | Unknown value for parameter.
*
* @flag MCIERR_BAD_INTEGER | Invalid or missing integer in command.
*
* @flag MCIERR_DUPLICATE_FLAGS | A flag or value was specified twice.
*
* @flag MCIERR_MISSING_COMMAND_STRING | No command was specified.
*
* @flag MCIERR_MISSING_DEVICE_NAME | No device name was specified.
*
* @flag MCIERR_MISSING_STRING_ARGUMENT | A string value was
* missing from the command.
*
* @flag MCIERR_NEW_REQUIRES_ALIAS | An alias must be used
* with the "new" device name.
*
* @flag MCIERR_NO_CLOSING_QUOTE | A closing quotation mark is missing.
*
* @flag MCIERR_NOTIFY_ON_AUTO_OPEN | The "notify" flag is illegal
* with auto-open.
*
* @flag MCIERR_PARAM_OVERFLOW | The output string was not long enough.
*
* @flag MCIERR_PARSER_INTERNAL | Internal parser error.
*
* @flag MCIERR_UNRECOGNIZED_KEYWORD | Unknown command parameter.
*
* @xref mciGetErrorString mciSendCommand
*/
MCIERROR APIENTRY mciSendStringA(
LPCSTR lpstrCommand,
LPSTR lpstrReturnString,
UINT uReturnLength,
HWND hwndCallback)
{
MCIERROR mciErr;
LPWSTR lpwstrCom;
LPWSTR lpwstrRet;
LPSTR lpstrTmp;
UINT len;
#ifdef DBG
dprintf4(( "Entered mciSendString ASCII" ));
#endif
// uReturnLength is a character count
// len is now in bytes
// WARNING: The length field might only be valid if a return
// address is given. If NO return address is specified, then
// we do not want to waste time allocating anything.
if (!lpstrReturnString) {
uReturnLength = 0;
}
len = BYTE_GIVEN_CHAR( uReturnLength );
// We could make the following code slightly more efficient by
// allocating a single area of size uReturnLength*2 bytes.
if (len) {
lpstrTmp = (LPSTR)mciAlloc( len );
if ( lpstrTmp == (LPSTR)NULL ) {
return MCIERR_OUT_OF_MEMORY;
}
lpwstrRet = (LPWSTR)mciAlloc( len );
if ( lpwstrRet == (LPWSTR)NULL ) {
mciFree( lpstrTmp );
return MCIERR_OUT_OF_MEMORY;
}
} else {
lpstrTmp = NULL;
lpwstrRet = NULL;
}
lpwstrCom = AllocUnicodeStr( (LPSTR)lpstrCommand );
if ( lpwstrCom == NULL ) {
if (len) {
mciFree( lpstrTmp );
mciFree( lpwstrRet );
}
return MCIERR_OUT_OF_MEMORY;
}
#ifdef DBG
dprintf4(( "Unicode Command = %ls", lpwstrCom ));
dprintf4(( "Ascii command = %s", lpstrCommand ));
#endif
mciErr = mciSendStringW( lpwstrCom, lpwstrRet, uReturnLength, hwndCallback );
dprintf4(( "mciSendStringW returned %d", mciErr ));
if (len) {
dprintf4(( "Copying Unicode string to Ascii: %ls", lpwstrRet));
UnicodeStrToAsciiStr( (PBYTE)lpstrTmp, (PBYTE)lpstrTmp + len, lpwstrRet );
strncpy( lpstrReturnString, lpstrTmp, uReturnLength );
dprintf4(( "........done: %s", lpstrReturnString));
mciFree( lpstrTmp );
mciFree( lpwstrRet );
}
FreeUnicodeStr( lpwstrCom );
return mciErr;
}
MCIERROR APIENTRY mciSendStringW(
LPCWSTR lpstrCommand,
LPWSTR lpstrReturnString,
UINT uReturnLength,
HWND hwndCallback)
{
MCIERROR wRet;
// Initialize the device list
if (!MCI_bDeviceListInitialized && !mciInitDeviceList()) {
return MCIERR_OUT_OF_MEMORY;
}
//
// We can get return code MCIERR_AUTO_ALREADY_CLOSED if the device is
// auto-open and appears to be open but when we get to the mciWindow
// thread it has already closed. In this case we try again.
//
do {
wRet = mciSendStringInternal (lpstrCommand, lpstrReturnString,
uReturnLength, hwndCallback, NULL);
} while (wRet == MCIERR_AUTO_ALREADY_CLOSED);
return wRet;
}
/*
* @doc INTERNAL MCI
*
* @api BOOL | mciExecute | This function is a simplified version of the
* <f mciSendString> function. It does not take a buffer for
* return information, and displays a dialog box when
* errors occur.
*
* @parm LPCSTR | lpstrCommand | Points to an MCI command string of the form:
* [command] [device] [parameters].
*
* @rdesc TRUE if successful, FALSE if unsuccessful.
*
* @comm This function provides a simple interface to MCI from scripting
* languages. For debugging, set the "mciexecute" entry in the
* [mmdebug] section of WIN.INI to 1 and detailed error information will
* be displayed in a dialog box. If "mmcmd" is set to 0, only user-correctable
* error information will be displayed.
* THIS FUNCTION IS NOW OBSOLETE AND IS ONLY PRESENT FOR 16BIT COMPATIBILITY
* HENCE NO UNICODE VERSION IS PROVIDED
*
* @xref mciSendString
*/
BOOL APIENTRY mciExecute(
LPCSTR lpstrCommand)
{
WCHAR aszError[MAXERRORLENGTH];
DWORD dwErr;
HANDLE hName = 0;
LPWSTR lpstrName = NULL;
LPWSTR lpwstrCom;
lpwstrCom = AllocUnicodeStr( (LPSTR)lpstrCommand );
if ( lpwstrCom == NULL ) {
return FALSE;
}
dwErr = mciSendStringW(lpwstrCom, NULL, 0, NULL);
FreeUnicodeStr( lpwstrCom );
if (LOWORD(dwErr) == 0) {
return TRUE;
}
if (!mciGetErrorStringW( dwErr, aszError, MAXERRORLENGTH )) {
LoadStringW( ghInst, STR_MCIUNKNOWN, aszError, MAXERRORLENGTH );
} else {
if (lpwstrCom != NULL)
{
// Skip initial blanks
while (*lpwstrCom == ' ') {
++lpwstrCom;
}
// Then skip the command
while (*lpwstrCom != ' ' && *lpwstrCom != '\0') {
++lpwstrCom;
}
// Then blanks before the device name
while (*lpwstrCom == ' ') ++lpwstrCom;
// Now, get the device name
if ( *lpwstrCom != '\0' &&
mciEatToken ((LPCWSTR *)&lpwstrCom, ' ', &lpstrName, FALSE)
!= 0
) {
dprintf1(("Could not allocate device name text for error box"));
}
}
}
MessageBoxW( NULL, aszError, lpstrName, MB_ICONHAND | MB_OK);
if (lpstrName != NULL) {
mciFree(lpstrName);
}
return FALSE;
}
/*
* @doc EXTERNAL MCI
*
* @api BOOL | mciGetErrorString | This function returns a
* textual description of the specified MCI error.
*
* @parm DWORD | dwError | Specifies the error code returned by
* <f mciSendCommand> or <f mciSendString>.
*
* @parm LPTSTR | lpstrBuffer | Specifies a pointer to a buffer that is
* filled with a textual description of the specified error.
*
* @parm UINT | uLength | Specifies the length of the buffer pointed to by
* <p lpstrBuffer>.
*
* @rdesc Returns TRUE if successful. Otherwise, the given error code
* was not known.
*/
BOOL APIENTRY mciGetErrorStringA(
DWORD dwError,
LPSTR lpstrBuffer,
UINT uLength)
{
HANDLE hInst = 0;
if (lpstrBuffer == NULL) {
return FALSE;
}
// If the high bit is set then get the error string from the driver
// otherwise get it from mmsystem.dll
if (HIWORD(dwError) != 0) {
mciEnter("mciGetErrorStringA");
if (MCI_VALID_DEVICE_ID ((UINT)HIWORD(dwError))) {
hInst = MCI_lpDeviceList[HIWORD (dwError)]->hDriver;
}
mciLeave("mciGetErrorStringA");
if (hInst == 0) {
hInst = ghInst;
dwError = MCIERR_DRIVER;
}
} else {
hInst = ghInst;
}
if (LoadStringA(hInst, LOWORD(dwError), lpstrBuffer, uLength ) == 0)
{
// If the string load failed then at least terminate the string
if (uLength > 0) {
*lpstrBuffer = '\0';
dprintf1(("Failed to load resource string"));
}
return FALSE;
}
else
{
return TRUE;
}
}
BOOL APIENTRY mciGetErrorStringW(
DWORD dwError,
LPWSTR lpstrBuffer,
UINT uLength)
{
HANDLE hInst = 0;
if (lpstrBuffer == NULL) {
return FALSE;
}
// If the high bit is set then get the error string from the driver
// otherwise get it from mmsystem.dll
if (HIWORD(dwError) != 0) {
mciEnter("mciGetErrorStringW");
if (MCI_VALID_DEVICE_ID ((UINT)HIWORD(dwError))) {
hInst = MCI_lpDeviceList[HIWORD (dwError)]->hDriver;
}
mciLeave("mciGetErrorStringW");
if (hInst == 0) {
hInst = ghInst;
dwError = MCIERR_DRIVER;
}
} else {
hInst = ghInst;
}
if (LoadStringW(hInst, LOWORD(dwError), lpstrBuffer, uLength ) == 0)
{
// If the string load failed then at least terminate the string
if (uLength > 0) {
*lpstrBuffer = '\0';
dprintf1(("Failed to load resource string"));
}
return FALSE;
}
else
{
return TRUE;
}
}
#if 0
/*
* Return non-zero if load successful
*/
BOOL MCIInit()
{
return TRUE;
}
/*
* Return non-zero if load successful
*/
void MCITerminate()
{
/*
We would like to close all open devices here but cannot because of
unknown WEP order
*/
if (hMciHeap != NULL) {
HeapDestroy(hMciHeap);
}
hMciHeap = NULL;
}
#endif