Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1442 lines
40 KiB

  1. /*******************************Module*Header*********************************\
  2. * Module Name: mciparse.c
  3. *
  4. * Media Control Architecture Command Parser
  5. *
  6. * Created: 3/2/90
  7. * Author: DLL (DavidLe)
  8. *
  9. * History:
  10. *
  11. * Copyright (c) 1990 Microsoft Corporation
  12. *
  13. \******************************************************************************/
  14. #include <windows.h>
  15. #define MMNOMIDI
  16. #define MMNOWAVE
  17. #define MMNOSOUND
  18. #define MMNOTIMER
  19. #define MMNOJOY
  20. #define MMNOSEQ
  21. #include "mmsystem.h"
  22. #define NOMIDIDEV
  23. #define NOWAVEDEV
  24. #define NOTIMERDEV
  25. #define NOJOYDEV
  26. #define NOSEQDEV
  27. #define NOTASKDEV
  28. #include "mmddk.h"
  29. #include "mmsysi.h"
  30. #ifndef STATICFN
  31. #define STATICFN
  32. #endif
  33. #define WCODE UINT _based(_segname("_CODE"))
  34. extern char far szOpen[]; // in MCI.C
  35. #ifdef DEBUG_RETAIL
  36. extern int DebugmciSendCommand;
  37. #endif
  38. // Number of command tables registered, including "holes"
  39. static UINT number_of_command_tables;
  40. // Command table list
  41. command_table_type command_tables[MAX_COMMAND_TABLES];
  42. static SZCODE szTypeTableExtension[] = ".mci";
  43. static SZCODE szCoreTable[] = "core";
  44. // Core table is loaded when the first MCI command table is requested
  45. static BOOL bCoreTableLoaded;
  46. // One element for each device type. Value is the table type to use
  47. // or 0 if there is no device type specific table.
  48. static WCODE table_types[] =
  49. {
  50. MCI_DEVTYPE_VCR, // vcr
  51. MCI_DEVTYPE_VIDEODISC, // videodisc
  52. MCI_DEVTYPE_OVERLAY, // overlay
  53. MCI_DEVTYPE_CD_AUDIO, // cdaudio
  54. MCI_DEVTYPE_DAT, // dat
  55. MCI_DEVTYPE_SCANNER, // scanner
  56. MCI_DEVTYPE_ANIMATION, // animation
  57. MCI_DEVTYPE_DIGITAL_VIDEO, // digitalvideo
  58. MCI_DEVTYPE_OTHER, // other
  59. MCI_DEVTYPE_WAVEFORM_AUDIO, // waveaudio
  60. MCI_DEVTYPE_SEQUENCER // sequencer
  61. };
  62. /*!!
  63. void PASCAL NEAR mciToLower (LPSTR lpstrString)
  64. {
  65. while (*lpstrString != '\0')
  66. {
  67. if (*lpstrString >= 'A' && *lpstrString <= 'Z')
  68. *lpstrString += 0x20;
  69. ++lpstrString;
  70. }
  71. }
  72. */
  73. /*
  74. * @doc INTERNAL MCI
  75. * @func UINT | mciEatCommandEntry | Read a command resource entry and
  76. * return its length and its value and identifier
  77. *
  78. * @parm LPCSTR | lpEntry | The start of the command resource entry
  79. *
  80. * @parm LPDWORD | lpValue | The value of the entry, returned to caller
  81. * May be NULL
  82. *
  83. * @parm UINT FAR* | lpID | The identifier of the entry, returned to caller
  84. * May be NULL
  85. *
  86. * @rdesc The total number of bytes in the entry
  87. *
  88. */
  89. UINT PASCAL NEAR mciEatCommandEntry (
  90. LPCSTR lpEntry,
  91. LPDWORD lpValue,
  92. UINT FAR* lpID)
  93. {
  94. LPCSTR lpScan = lpEntry;
  95. while (*lpScan++ != '\0')
  96. ;
  97. if (lpValue != NULL)
  98. *lpValue = *(LPDWORD)lpScan;
  99. lpScan += sizeof(DWORD);
  100. if (lpID != NULL)
  101. *lpID = *(LPWORD)lpScan;
  102. lpScan += sizeof(UINT);
  103. return lpScan - lpEntry;
  104. }
  105. // Return the size used by this token in the parameter list
  106. UINT PASCAL NEAR mciGetParamSize (DWORD dwValue, UINT wID)
  107. {
  108. switch (wID)
  109. {
  110. case MCI_CONSTANT:
  111. case MCI_INTEGER:
  112. case MCI_STRING:
  113. return sizeof(DWORD);
  114. case MCI_RECT:
  115. return sizeof(RECT);
  116. case MCI_RETURN:
  117. switch ((UINT)dwValue)
  118. {
  119. case MCI_INTEGER:
  120. return sizeof(DWORD);
  121. case MCI_STRING:
  122. case MCI_RECT:
  123. return sizeof(RECT);
  124. default:
  125. DOUT ("mciGetParamSize: Unknown return type\r\n");
  126. return 0;
  127. }
  128. break;
  129. }
  130. return 0;
  131. }
  132. /*
  133. * @doc INTERNAL MCI
  134. * @func UINT | mciRegisterCommandTable | This function adds a new
  135. * table for the MCI parser.
  136. *
  137. * @parm HGLOBAL | hResource | Handle to the RCDATA resource
  138. *
  139. * @parm UINT FAR* | lpwIndex | Pointer to command table index
  140. *
  141. * @parm UINT | wType | Specifies the device type for this command table.
  142. * Driver tables and the core table are type 0.
  143. *
  144. * @parm HINSTANCE | hModule | Module instance registering table.
  145. *
  146. * @rdesc Returns the command table index number that was assigned or -1
  147. * on error.
  148. *
  149. */
  150. STATICFN UINT PASCAL NEAR
  151. mciRegisterCommandTable(
  152. HGLOBAL hResource,
  153. UINT FAR* lpwIndex,
  154. UINT wType,
  155. HINSTANCE hModule
  156. )
  157. {
  158. UINT wID;
  159. /* First check for free slots */
  160. for (wID = 0; wID < number_of_command_tables; ++wID)
  161. if (command_tables[wID].hResource == NULL)
  162. break;
  163. /* If no empty slots then allocate another one */
  164. if (wID >= number_of_command_tables)
  165. {
  166. if (number_of_command_tables == MAX_COMMAND_TABLES)
  167. {
  168. DOUT ("mciRegisterCommandTable: No more tables\r\n");
  169. return (UINT)-1;
  170. }
  171. else
  172. wID = number_of_command_tables++;
  173. }
  174. /* Fill in the slot */
  175. command_tables[wID].wType = wType;
  176. command_tables[wID].hResource = hResource;
  177. command_tables[wID].lpwIndex = lpwIndex;
  178. command_tables[wID].hModule = hModule;
  179. #ifdef DEBUG
  180. command_tables[wID].wLockCount = 0;
  181. #endif
  182. #ifdef DEBUG
  183. if (DebugmciSendCommand > 2)
  184. {
  185. DPRINTF(("mciRegisterCommandTable INFO: assigned slot %u\r\n", wID));
  186. DPRINTF(("mciRegisterCommandTable INFO: #tables is %u\r\n",
  187. number_of_command_tables));
  188. }
  189. #endif
  190. return wID;
  191. }
  192. /*
  193. * @doc DDK MCI
  194. * @api UINT | mciLoadCommandResource | Registers the indicated
  195. * resource as an MCI command table and builds a command table
  196. * index. If a file with the resource name and the extension '.mci' is
  197. * found in the path then the resource is taken from that file.
  198. *
  199. * @parm HINSTANCE | hInstance | The instance of the module whose executable
  200. * file contains the resource. This parameter is ignored if an external file
  201. * is found.
  202. *
  203. * @parm LPCSTR | lpResName | The name of the resource
  204. *
  205. * @parm UINT | wType | The table type. Custom device specific tables MUST
  206. * give a table type of 0.
  207. *
  208. * @rdesc Returns the command table index number that was assigned or -1
  209. * on error.
  210. *
  211. */
  212. UINT WINAPI mciLoadCommandResource (
  213. HINSTANCE hInstance,
  214. LPCSTR lpResName,
  215. UINT wType)
  216. {
  217. UINT FAR* lpwIndex, FAR* lpwScan;
  218. HINSTANCE hExternal;
  219. HRSRC hResInfo;
  220. HGLOBAL hResource;
  221. LPSTR lpResource, lpScan;
  222. int nCommands = 0;
  223. UINT wID;
  224. UINT wLen;
  225. // Name + '.' + Extension + '\0'
  226. char strFile[8 + 1 + 3 + 1];
  227. LPSTR lpstrFile = strFile;
  228. LPCSTR lpstrType = lpResName;
  229. OFSTRUCT ofs;
  230. #ifdef DEBUG
  231. if (DebugmciSendCommand > 2)
  232. DPRINTF(("mciLoadCommandResource INFO: %s loading\r\n", (LPSTR)lpResName));
  233. #endif
  234. // Initialize the device list
  235. if (!MCI_bDeviceListInitialized && !mciInitDeviceList())
  236. return MCIERR_OUT_OF_MEMORY;
  237. // Load the core table if its not already there
  238. if (!bCoreTableLoaded)
  239. {
  240. bCoreTableLoaded = TRUE;
  241. // If its not the core table being loaded
  242. if (lstrcmpi (szCoreTable, lpResName) != 0)
  243. if (mciLoadCommandResource (ghInst, szCoreTable, 0) == -1)
  244. {
  245. DOUT ("mciLoadCommandResource: Cannot load core table\r\n");
  246. }
  247. }
  248. // Check for a file with the extension ".mci"
  249. // Copy up to the first eight characters of device type
  250. while (lpstrType < lpResName + 8 && *lpstrType != '\0')
  251. *lpstrFile++ = *lpstrType++;
  252. // Tack extension onto end
  253. lstrcpy (lpstrFile, szTypeTableExtension);
  254. // If the file exists and can be loaded then set flag
  255. // otherwise load resource from MMSYSTEM.DLL
  256. if (OpenFile (strFile, &ofs, OF_EXIST) == HFILE_ERROR ||
  257. (hExternal = LoadLibrary(strFile)) < HINSTANCE_ERROR)
  258. hExternal = NULL;
  259. // Load the given table from the file or from the module if not found
  260. if (hExternal != NULL &&
  261. (hResInfo = FindResource (hExternal, lpResName, RT_RCDATA)) != NULL)
  262. hInstance = hExternal;
  263. else
  264. hResInfo = FindResource (hInstance, lpResName, RT_RCDATA);
  265. if (hResInfo == NULL)
  266. {
  267. DOUT ("mciLoadCommandResource: Cannot find command resource\r\n");
  268. return (UINT)-1;
  269. }
  270. if ((hResource = LoadResource (hInstance, hResInfo)) == NULL)
  271. {
  272. DOUT ("mciLoadCommandResource: Cannot load command resource\r\n");
  273. return (UINT)-1;
  274. }
  275. if ((lpResource = LockResource (hResource)) == NULL)
  276. {
  277. DOUT ("mciLoadCommandResource: Cannot lock resource\r\n");
  278. FreeResource (hResource);
  279. return (UINT)-1;
  280. }
  281. /* Count the number of commands */
  282. lpScan = lpResource;
  283. while (TRUE)
  284. {
  285. lpScan += mciEatCommandEntry(lpScan, NULL, &wID);
  286. // End of command?
  287. if (wID == MCI_COMMAND_HEAD)
  288. ++nCommands;
  289. // End of command list?
  290. else if (wID == MCI_END_COMMAND_LIST)
  291. break;
  292. }
  293. // There must be at least on command in the table
  294. if (nCommands == 0)
  295. {
  296. DOUT ("mciLoadCommandResource: No commands in the specified table\r\n");
  297. UnlockResource (hResource);
  298. FreeResource (hResource);
  299. return (UINT)-1;
  300. }
  301. // Allocate storage for the command table index
  302. // Leave room for a -1 entry to terminate it
  303. if ((lpwIndex = (UINT FAR*)
  304. mciAlloc ((UINT)sizeof (UINT) * (nCommands + 1)))
  305. == NULL)
  306. {
  307. DOUT ("mciLoadCommandResource: cannot allocate command table index\r\n");
  308. UnlockResource (hResource);
  309. FreeResource (hResource);
  310. return (UINT)-1;
  311. }
  312. /* Build Command Table */
  313. lpwScan = lpwIndex;
  314. lpScan = lpResource;
  315. while (TRUE)
  316. {
  317. // Get next command entry
  318. wLen = mciEatCommandEntry (lpScan, NULL, &wID);
  319. if (wID == MCI_COMMAND_HEAD)
  320. // Add an index to this command
  321. *lpwScan++ = lpScan - lpResource;
  322. else if (wID == MCI_END_COMMAND_LIST)
  323. {
  324. // Mark the end of the table
  325. *lpwScan = (UINT)-1;
  326. break;
  327. }
  328. lpScan += wLen;
  329. }
  330. UnlockResource (hResource);
  331. return mciRegisterCommandTable (hResource, lpwIndex, wType, hExternal);
  332. }
  333. /*
  334. * @doc INTERNAL MCI
  335. * @func UINT | mciLoadTableType | If the table of the given type
  336. * has not been loaded, register it
  337. *
  338. * @parm UINT | wType | The table type to load
  339. *
  340. * @rdesc Returns the command table index number that was assigned or -1
  341. * on error.
  342. */
  343. UINT PASCAL NEAR mciLoadTableType (
  344. UINT wType)
  345. {
  346. UINT wID;
  347. char buf[MCI_MAX_DEVICE_TYPE_LENGTH];
  348. int nTypeLen;
  349. // Check to see if this table type is already loaded
  350. for (wID = 0; wID < number_of_command_tables; ++wID)
  351. if (command_tables[wID].wType == wType)
  352. return wID;
  353. // Must load table
  354. // First look up what device type specific table to load for this type
  355. if (wType < MCI_DEVTYPE_FIRST || wType > MCI_DEVTYPE_LAST)
  356. return (UINT)-1;
  357. // Load string that corresponds to table type
  358. LoadString (ghInst, table_types[wType - MCI_DEVTYPE_FIRST],
  359. buf, sizeof(buf));
  360. //Must be at least one character in type name
  361. if ((nTypeLen = lstrlen (buf)) < 1)
  362. return (UINT)-1;
  363. // Register the table with MCI
  364. return mciLoadCommandResource (ghInst, buf, wType);
  365. }
  366. /*
  367. * @doc DDK MCI
  368. *
  369. * @api BOOL | mciFreeCommandResource | Frees the memory used
  370. * by the specified command table.
  371. *
  372. * @parm UINT | wTable | The table index returned from a previous call to
  373. * mciLoadCommandResource.
  374. *
  375. * @rdesc FALSE if the table index is not valid, TRUE otherwise.
  376. *
  377. */
  378. BOOL WINAPI mciFreeCommandResource (
  379. UINT wTable)
  380. {
  381. UINT wID;
  382. HGLOBAL hResource;
  383. UINT FAR* lpwIndex;
  384. #ifdef DEBUG
  385. if (DebugmciSendCommand > 2)
  386. {
  387. DPRINTF(("mciFreeCommandResource INFO: Free table %d\r\n", wTable));
  388. DPRINTF(("mciFreeCommandResource INFO: Lockcount is %d\r\n",
  389. command_tables[wTable].wLockCount));
  390. }
  391. #endif
  392. /* Validate input -- do not let the core table be free'd */
  393. if (wTable <= 0 || wTable >= number_of_command_tables)
  394. {
  395. #ifdef DEBUG
  396. // wTable == -1 is OK
  397. if (wTable != -1)
  398. DOUT ("mciFreeCommandResource: Bad table number\r\n");
  399. #endif
  400. return FALSE;
  401. }
  402. // If this table is being used elsewhere then keep it around
  403. for (wID = 1; wID < MCI_wNextDeviceID; ++wID)
  404. if (MCI_lpDeviceList[wID] != NULL)
  405. if (MCI_lpDeviceList[wID]->wCustomCommandTable == wTable ||
  406. MCI_lpDeviceList[wID]->wCommandTable == wTable)
  407. {
  408. #ifdef DEBUG
  409. if (DebugmciSendCommand > 2)
  410. DOUT ("mciFreeCommandResource INFO: table in use\r\n");
  411. #endif
  412. return FALSE;
  413. }
  414. #if 0
  415. /* Search the list of tables */
  416. for (wID = 0; wID < number_of_command_tables; ++wID)
  417. /* If this resource is still in use, keep it around */
  418. if (command_tables[wID].hResource == hResource)
  419. {
  420. #ifdef DEBUG
  421. if (DebugmciSendCommand > 2)
  422. DOUT ("mciFreeCommandResource INFO: resource in use\r\n");
  423. #endif
  424. return FALSE;
  425. }
  426. #endif
  427. hResource = command_tables[wTable].hResource;
  428. command_tables[wTable].hResource = NULL;
  429. lpwIndex = command_tables[wTable].lpwIndex;
  430. command_tables[wTable].lpwIndex = NULL;
  431. command_tables[wTable].wType = 0;
  432. FreeResource (hResource);
  433. mciFree (lpwIndex);
  434. if (command_tables[wTable].hModule != NULL)
  435. FreeLibrary (command_tables[wTable].hModule);
  436. // Make space at top of list
  437. if (wTable == number_of_command_tables - 1)
  438. --number_of_command_tables;
  439. #ifdef DEBUG
  440. if (DebugmciSendCommand > 2)
  441. DPRINTF(("mciFreeCommandResource INFO: number_of_command_tables: %u\r\n",
  442. number_of_command_tables));
  443. #endif
  444. return TRUE;
  445. }
  446. #ifdef DEBUG
  447. void PASCAL NEAR mciCheckLocks (void)
  448. {
  449. UINT wTable;
  450. if (DebugmciSendCommand <= 2)
  451. return;
  452. for (wTable = 0; wTable < number_of_command_tables; ++wTable)
  453. {
  454. if (command_tables[wTable].hResource == NULL)
  455. continue;
  456. DPRINTF(("mciCheckLocks INFO: table %u ", wTable));
  457. DPRINTF(("user: %x ",
  458. GlobalFlags (command_tables[wTable].hResource) & GMEM_LOCKCOUNT));
  459. DPRINTF(("mci: %u ", command_tables[wTable].wLockCount));
  460. if (GlobalFlags (command_tables[wTable].hResource) & GMEM_DISCARDABLE)
  461. DPRINTF(("discardable\r\n"));
  462. else
  463. DPRINTF(("NOT discardable\r\n"));
  464. }
  465. }
  466. #endif
  467. /*
  468. * @doc INTERNAL MCI
  469. * @func BOOL | mciUnlockCommandTable | Unlocks the command table given by
  470. * a table index
  471. *
  472. * @parm UINT | wCommandTable | Table to unlock
  473. *
  474. * @rdesc TRUE if success, FALSE otherwise
  475. *
  476. * @comm Used external to this module by mci.c
  477. *
  478. */
  479. BOOL PASCAL NEAR mciUnlockCommandTable (
  480. UINT wCommandTable)
  481. {
  482. UnlockResource(command_tables[wCommandTable].hResource);
  483. #ifdef DEBUG
  484. --command_tables[wCommandTable].wLockCount;
  485. if (DebugmciSendCommand > 2)
  486. {
  487. DPRINTF(("mciUnlockCommandTable INFO: table %d\r\n", wCommandTable));
  488. DOUT ("mciUnlockCommandTable INFO: check locks...\r\n");
  489. mciCheckLocks();
  490. }
  491. #endif
  492. return TRUE;
  493. }
  494. /*
  495. * @doc INTERNAL MCI
  496. * @func LPSTR | FindCommandInTable | Look up the given
  497. * command string in the GIVEN parser command table
  498. *
  499. * @parm UINT | wTable | Command table to use
  500. *
  501. * @parm LPCSTR | lpstrCommand | The command to look up. It must
  502. * be in lower case with no leading or trailing blanks and with at
  503. * least one character.
  504. *
  505. * @parm UINT FAR * | lpwMessage | The message corresponding to the command
  506. * Returned to caller.
  507. *
  508. * @rdesc NULL if the command is unknown or on error, otherwise a pointer to
  509. * the command list for the input command string.
  510. *
  511. * @comm If the command is found, the command resource will be locked on exit.
  512. *
  513. */
  514. LPSTR PASCAL NEAR FindCommandInTable (
  515. UINT wTable,
  516. LPCSTR lpstrCommand,
  517. UINT FAR * lpwMessage)
  518. {
  519. UINT FAR* lpwIndex;
  520. LPSTR lpResource, lpstrThisCommand;
  521. UINT wMessage;
  522. /* Validate table */
  523. if (wTable >= number_of_command_tables)
  524. {
  525. // Check the core table but its not yet loaded
  526. if (wTable == 0)
  527. {
  528. // Try to load it
  529. if (mciLoadCommandResource (ghInst, szCoreTable, 0) == -1)
  530. {
  531. DOUT ("FindCommandInTable: cannot load core table\r\n");
  532. return NULL;
  533. }
  534. } else
  535. {
  536. DOUT ("MCI FindCommandInTable: invalid table ID\r\n");
  537. return NULL;
  538. }
  539. }
  540. if ((lpResource = LockResource (command_tables[wTable].hResource)) == NULL)
  541. {
  542. DOUT ("MCI FindCommandInTable: Cannot lock table resource\r\n");
  543. return NULL;
  544. }
  545. #ifdef DEBUG
  546. ++command_tables[wTable].wLockCount;
  547. #endif
  548. // Look at each command in the table
  549. lpwIndex = command_tables[wTable].lpwIndex;
  550. if (lpwIndex == NULL)
  551. {
  552. DOUT ("MCI FindCommandInTable: null command table index\r\n");
  553. return NULL;
  554. }
  555. while (*lpwIndex != -1)
  556. {
  557. DWORD dwMessage;
  558. lpstrThisCommand = *lpwIndex++ + lpResource;
  559. // Get message number from the table
  560. mciEatCommandEntry (lpstrThisCommand, &dwMessage, NULL);
  561. wMessage = (UINT)dwMessage;
  562. // Does this command match the input?
  563. // String case
  564. if (HIWORD (lpstrCommand) != 0 &&
  565. lstrcmpi (lpstrThisCommand, lpstrCommand) == 0 ||
  566. // Message case
  567. HIWORD (lpstrCommand) == 0 &&
  568. wMessage == LOWORD ((DWORD)lpstrCommand))
  569. {
  570. // Retain the locked resource pointer
  571. command_tables[wTable].lpResource = lpResource;
  572. // Address the message ID which comes after the command name
  573. if (lpwMessage != NULL)
  574. *lpwMessage = wMessage;
  575. // Leave table locked on exit
  576. return lpstrThisCommand;
  577. }
  578. // Strings don't match, go to the next command in the table
  579. }
  580. UnlockResource (command_tables[wTable].hResource);
  581. #ifdef DEBUG
  582. --command_tables[wTable].wLockCount;
  583. #endif
  584. return NULL;
  585. }
  586. /*
  587. * @doc INTERNAL MCI
  588. * @func LPSTR | FindCommandItem | Look up the given
  589. * command string in the parser command tables
  590. *
  591. * @parm UINT | wDeviceID | The device ID used for this command.
  592. * If 0 then only the system core command table is searched.
  593. *
  594. * @parm LPCSTR | lpstrType | The type name of the device
  595. *
  596. * @parm LPCSTR | lpstrCommand | The command to look up. It must
  597. * be in lower case with no leading or trailing blanks and with at
  598. * least one character. If the HIWORD is 0 then the LOWORD contains
  599. * the command message ID instead of a command name and the function is
  600. * merely to find the command list pointer.
  601. *
  602. * If the high word is 0 then the low word is an command ID value instead
  603. * of a command name
  604. *
  605. * @parm UINT FAR* | lpwMessage | The message corresponding to the command
  606. * Returned to caller.
  607. *
  608. * @parm UINT FAR* | lpwTable | The table index in which the command was found
  609. * Returned to caller.
  610. *
  611. * @rdesc NULL if the command is unknown, otherwise a pointer to
  612. * the command list for the input command string.
  613. */
  614. LPSTR PASCAL NEAR FindCommandItem (
  615. UINT wDeviceID,
  616. LPCSTR lpstrType,
  617. LPCSTR lpstrCommand,
  618. UINT FAR* lpwMessage,
  619. UINT FAR* lpwTable)
  620. {
  621. LPSTR lpCommand;
  622. UINT wTable;
  623. LPMCI_DEVICE_NODE nodeWorking;
  624. // Only check hiword per comments above
  625. if (HIWORD (lpstrCommand) != NULL && *lpstrCommand == '\0')
  626. {
  627. DOUT ("MCI FindCommandItem: lpstrCommand is NULL or empty string\r\n");
  628. return NULL;
  629. }
  630. // If a specific device ID was specified then look in any custom table
  631. // or type table
  632. if (wDeviceID != 0 && wDeviceID != MCI_ALL_DEVICE_ID)
  633. {
  634. // If the device ID is valid
  635. if (!MCI_VALID_DEVICE_ID(wDeviceID))
  636. {
  637. DOUT ("MCI FindCommandItem: Invalid device ID\r\n");
  638. return NULL;
  639. }
  640. nodeWorking = MCI_lpDeviceList[wDeviceID];
  641. // If there is a custom command table then use it
  642. if ((wTable = nodeWorking->wCustomCommandTable) != -1)
  643. {
  644. lpCommand = FindCommandInTable (wTable, lpstrCommand, lpwMessage);
  645. if (lpCommand != NULL)
  646. goto exit;
  647. }
  648. // Get the device type table from the existing device
  649. // Relies on mciReparseCommand in mciLoadDevice to catch all device type
  650. // tables when device is not yet open.
  651. if ((wTable = nodeWorking->wCommandTable) != -1)
  652. {
  653. lpCommand = FindCommandInTable (wTable, lpstrCommand, lpwMessage);
  654. if (lpCommand != NULL)
  655. goto exit;
  656. }
  657. }
  658. // If no match was found in the device or type specific tables
  659. // Look in the core table
  660. wTable = 0;
  661. lpCommand = FindCommandInTable (wTable, lpstrCommand, lpwMessage);
  662. if (lpCommand == NULL)
  663. wTable = (UINT)-1;
  664. exit:
  665. if (lpwTable != NULL)
  666. *lpwTable = wTable;
  667. #ifdef DEBUG
  668. if (DebugmciSendCommand > 2)
  669. {
  670. DOUT ("FindCommandItem INFO: check locks...\r\n");
  671. mciCheckLocks();
  672. }
  673. #endif
  674. return lpCommand;
  675. }
  676. /*
  677. * @doc INTERNAL MCI
  678. * @func LPSTR | mciCheckToken | Check to see if the command item matches
  679. * the given string, allowing multiple blanks in the input parameter to
  680. * match a corresponding single blank in the command token and ignoring
  681. * case.
  682. *
  683. * @parm LPCSTR | lpstrToken | The command token to check
  684. *
  685. * @parm LPCSTR | lpstrParam | The input parameter
  686. *
  687. * @rdesc NULL if no match, otherwise points to the first character
  688. * after the parameter
  689. *
  690. */
  691. STATICFN LPSTR PASCAL NEAR
  692. mciCheckToken(
  693. LPCSTR lpstrToken,
  694. LPCSTR lpstrParam
  695. )
  696. {
  697. /* Check for legal input */
  698. if (lpstrToken == NULL || lpstrParam == NULL)
  699. return NULL;
  700. while (*lpstrToken != '\0' && MCI_TOLOWER(*lpstrParam) == *lpstrToken)
  701. {
  702. // If the token contains a blank, allow more than one blank in the
  703. // parameter
  704. if (*lpstrToken == ' ')
  705. while (*lpstrParam == ' ')
  706. ++lpstrParam;
  707. else
  708. *lpstrParam++;
  709. *lpstrToken++;
  710. }
  711. if (*lpstrToken != '\0'|| (*lpstrParam != '\0' && *lpstrParam != ' '))
  712. return NULL;
  713. else
  714. return (LPSTR)lpstrParam;
  715. }
  716. /*
  717. * @doc INTERNAL MCI
  718. * @api BOOL | mciParseInteger | Parse the given integer
  719. *
  720. * @parm LPSTR FAR * | lplpstrInput | The string containing the argument.
  721. * It is updated and returned to the caller pointing to the first character
  722. * after the argument or to the first character that is in error.
  723. *
  724. * @parm LPDWORD | lpdwArgument | The place to put the output
  725. *
  726. * @rdesc Returns TRUE if not error
  727. *
  728. * @comm If there are colons in the input (':') the result is "colonized".
  729. * This means that each time a colon is read, the current result is written
  730. * and any subsequent digits are shifted left one byte. No one "segment"
  731. * can be more than 0xFF. For example, "0:1:2:3" is parsed to 0x03020100.
  732. *
  733. */
  734. #pragma warning(4:4146)
  735. STATICFN BOOL PASCAL NEAR
  736. mciParseInteger(
  737. LPSTR FAR * lplpstrInput,
  738. LPDWORD lpdwArgument
  739. )
  740. {
  741. LPSTR lpstrInput = *lplpstrInput;
  742. BOOL fDigitFound;
  743. DWORD dwResult;
  744. LPSTR lpstrResult = (LPSTR)lpdwArgument;
  745. int nDigitPosition = 0;
  746. BOOL bSigned = FALSE;
  747. // Leading blanks have been removed by mciParseParams
  748. if (*lpstrInput == '-')
  749. {
  750. ++lpstrInput;
  751. bSigned = TRUE;
  752. }
  753. // Read digits
  754. *lpdwArgument = 0; /* Initialize */
  755. dwResult = 0;
  756. fDigitFound = FALSE; /* Initialize */
  757. while (*lpstrInput >= '0' && *lpstrInput <= '9' || *lpstrInput == ':')
  758. {
  759. // ':' indicates colonized data
  760. if (*lpstrInput == ':')
  761. {
  762. // Cannot mix colonized and signed forms
  763. if (bSigned)
  764. {
  765. DOUT ("Bad integer: mixing signed and colonized forms\r\n");
  766. return FALSE;
  767. }
  768. // Check for overflow in accumulated colonized byte
  769. if (dwResult > 0xFF)
  770. return FALSE;
  771. // Copy and move to next byte converted in output
  772. *lpstrResult++ = (char)dwResult;
  773. ++lpstrInput;
  774. // Initialize next colonized byte
  775. dwResult = 0;
  776. ++nDigitPosition;
  777. // Only allow four colonized components
  778. if (nDigitPosition > 3)
  779. {
  780. DOUT ("Bad integer: Too many colonized components\r\n");
  781. return FALSE;
  782. }
  783. } else
  784. {
  785. char cDigit = (char)(*lpstrInput++ - '0');
  786. // Satisfies condition that at least one digit must be read
  787. fDigitFound = TRUE;
  788. if (dwResult > 0xFFFFFFFF / 10)
  789. // Overflow if multiply was to occur
  790. return FALSE;
  791. else
  792. // Multiply for next digit
  793. dwResult *= 10;
  794. // Add new digit
  795. dwResult += cDigit;
  796. }
  797. }
  798. if (nDigitPosition == 0)
  799. {
  800. // No colonized components
  801. if (bSigned)
  802. {
  803. // Check for overflow from negation
  804. if (dwResult > 0x7FFFFFFF)
  805. return FALSE;
  806. // Negate result because a '-' sign was parsed
  807. dwResult = -dwResult;
  808. }
  809. *lpdwArgument = dwResult;
  810. }
  811. else
  812. // Store last colonized component
  813. {
  814. // Check for overflow
  815. if (dwResult > 0xFF)
  816. return FALSE;
  817. // Store component
  818. *lpstrResult = (char)dwResult;
  819. }
  820. *lplpstrInput = lpstrInput;
  821. /*
  822. If there were no digits or if the digits were followed by a character
  823. other than a blank or a '\0', then return a syntax error.
  824. */
  825. return fDigitFound && (!*lpstrInput || *lpstrInput == ' ');
  826. }
  827. /*
  828. * @doc INTERNAL MCI
  829. * @func BOOL | mciParseConstant | Parse the given integer
  830. *
  831. * @parm LPSTR FAR * | lplpstrInput | The string containing the argument.
  832. * It is updated and returned to the caller pointing to the first character
  833. * after the argument or to the first character that is in error.
  834. *
  835. * @parm LPDWORD | lpdwArgument | The place to put the output
  836. *
  837. * @parm LPSTR | lpItem | Pointer into command table.
  838. *
  839. * @rdesc Returns TRUE if not error
  840. *
  841. */
  842. STATICFN BOOL PASCAL NEAR
  843. mciParseConstant(
  844. LPSTR FAR * lplpstrInput,
  845. LPDWORD lpdwArgument,
  846. LPSTR lpItem
  847. )
  848. {
  849. LPSTR lpPrev;
  850. DWORD dwValue;
  851. UINT wID;
  852. // Skip past constant header
  853. lpItem += mciEatCommandEntry (lpItem, &dwValue, &wID);
  854. while (TRUE)
  855. {
  856. LPSTR lpstrAfter;
  857. lpPrev = lpItem;
  858. lpItem += mciEatCommandEntry (lpItem, &dwValue, &wID);
  859. if (wID == MCI_END_CONSTANT)
  860. break;
  861. if ((lpstrAfter = mciCheckToken (lpPrev, *lplpstrInput)) != NULL)
  862. {
  863. *lpdwArgument = dwValue;
  864. *lplpstrInput = lpstrAfter;
  865. return TRUE;
  866. }
  867. }
  868. return mciParseInteger (lplpstrInput, lpdwArgument);
  869. }
  870. /*
  871. * @doc INTERNAL MCI
  872. * @func UINT | mciParseArgument | Parse the given argument
  873. *
  874. * @parm DWORD | dwValue | The argument value
  875. *
  876. * @parm UINT | wID | The argument ID
  877. *
  878. * @parm LPSTR FAR * | lplpstrOutput | The string containing the argument.
  879. * It is updated and returned to the caller pointing to the first character
  880. * after the argument or to the first character that is in error.
  881. *
  882. * @parm LPDWORD | lpdwFlags | The output flags
  883. *
  884. * @parm LPDWORD | lpArgument | The place to put the output
  885. *
  886. * @rdesc Returns 0 if no error or
  887. * @flag MCIERR_BAD_INTEGER | An integer argument could not be parsed
  888. * @flag MCIERR_MISSING_STRING_ARGUMENT | An expected string argument
  889. * @flag MCIERR_PARM_OVERFLOW | The output buffer was a NULL pointer
  890. * was missing
  891. *
  892. */
  893. STATICFN UINT PASCAL NEAR
  894. mciParseArgument(
  895. DWORD dwValue,
  896. UINT wID,
  897. LPSTR FAR * lplpstrOutput,
  898. LPDWORD lpdwFlags,
  899. LPSTR lpArgument,
  900. LPSTR lpCurrentCommandItem
  901. )
  902. {
  903. LPSTR lpstrInput = *lplpstrOutput;
  904. UINT wRetval = 0;
  905. /* Switch on the argument type */
  906. switch (wID)
  907. {
  908. // The parameter is a flag
  909. case MCI_FLAG:
  910. break; /* switch */
  911. case MCI_CONSTANT:
  912. if (*lpstrInput == '\0')
  913. wRetval = MCIERR_NO_INTEGER;
  914. else if (!mciParseConstant (&lpstrInput, (LPDWORD)lpArgument,
  915. lpCurrentCommandItem))
  916. wRetval = MCIERR_BAD_CONSTANT;
  917. break;
  918. /* The parameter has an integer argument, try to parse it */
  919. case MCI_INTEGER:
  920. if (!mciParseInteger (&lpstrInput, (LPDWORD)lpArgument))
  921. wRetval = MCIERR_BAD_INTEGER;
  922. break; /* switch */
  923. case MCI_RECT:
  924. {
  925. // Read in four RECT components. Resulting structure is the
  926. // same as a Windows RECT
  927. long lTemp;
  928. int n;
  929. for (n = 0; n < 4; ++n)
  930. {
  931. if (!mciParseInteger (&lpstrInput, &lTemp))
  932. {
  933. wRetval = MCIERR_BAD_INTEGER;
  934. break;
  935. }
  936. // Each component is a signed 16 bit number
  937. if (lTemp > 32768 || lTemp < -32767)
  938. {
  939. wRetval = MCIERR_BAD_INTEGER;
  940. break;
  941. }
  942. ((int FAR *)lpArgument)[n] = (int)lTemp;
  943. // Remove leading blanks before next digit
  944. while (*lpstrInput == ' ') ++lpstrInput;
  945. }
  946. break;
  947. }
  948. case MCI_STRING:
  949. {
  950. LPSTR lpstrOutput;
  951. /* The parameter has an string argument, read it */
  952. // Leading blanks have been removed by mciParseParams
  953. /* Are there any non-blank characters left in the input? */
  954. if (*lpstrInput == '\0')
  955. {
  956. /* Return an error */
  957. wRetval = MCIERR_MISSING_STRING_ARGUMENT;
  958. break; /* switch */
  959. }
  960. if ((wRetval = mciEatToken (&lpstrInput, ' ', &lpstrOutput, FALSE))
  961. != 0)
  962. {
  963. DOUT ("mciParseArgument: error parsing string\r\n");
  964. return wRetval;
  965. }
  966. *(LPDWORD)lpArgument = (DWORD)lpstrOutput;
  967. // NOTE: mciSendString frees the output string after command execution
  968. // by calling mciParserFree
  969. break; /* switch */
  970. } /* case */
  971. } /* switch */
  972. /* Update the output flags if there was no error */
  973. if (wRetval == 0)
  974. {
  975. if (*lpdwFlags & dwValue)
  976. {
  977. if (wID == MCI_CONSTANT)
  978. wRetval = MCIERR_FLAGS_NOT_COMPATIBLE;
  979. else
  980. wRetval = MCIERR_DUPLICATE_FLAGS;
  981. } else
  982. *lpdwFlags |= dwValue;
  983. }
  984. /*
  985. Return the input pointer pointing at the first character after
  986. the argument or to the first character that is in error
  987. */
  988. *lplpstrOutput = lpstrInput;
  989. return wRetval;
  990. }
  991. /*
  992. * @doc MCI INTERNAL
  993. * @func UINT | mciParseParams | Parse the command parameters
  994. *
  995. * @parm LPCSTR | lpstrParams | The parameter string
  996. *
  997. * @parm LPCSTR | lpCommandList | The command table description
  998. * of the command tokens
  999. *
  1000. * @parm LPDWORD | lpdwFlags | Return the parsed flags here
  1001. *
  1002. * @parm LPDWORD | lpdwOutputParams | Return the list of parameters here
  1003. *
  1004. * @parm UINT | wParamsSize | The size allocated for the parameter list
  1005. *
  1006. * @parm LPSTR FAR * FAR * | lpPointerList | A NULL terminated list of
  1007. * pointers allocated by this function that should be free'd when
  1008. * no longer needed. The list itself should be free'd also. In both
  1009. * cases, use mciFree().
  1010. *
  1011. * @parm UINT FAR* | lpwParsingError | If not NULL then if the command is
  1012. * 'open', unrecognized keywords return an error here, and the
  1013. * function return value is 0 (unless other errors occur). This
  1014. * is used to allow reparsing of the command by mciLoadDevice
  1015. *
  1016. * @rdesc Returns zero if successful or one of the following error codes:
  1017. * @flag MCIERR_PARM_OVERFLOW | Not enough space for parameters
  1018. * @flag MCIERR_UNRECOGNIZED_KEYWORD | Unrecognized keyword
  1019. *
  1020. * @comm Any syntax error, including missing arguments, will result in
  1021. * a non-zero error return and invalid output data.
  1022. *
  1023. */
  1024. UINT PASCAL NEAR
  1025. mciParseParams(
  1026. LPCSTR lpstrParams,
  1027. LPCSTR lpCommandList,
  1028. LPDWORD lpdwFlags,
  1029. LPSTR lpOutputParams,
  1030. UINT wParamsSize,
  1031. LPSTR FAR * FAR *lpPointerList,
  1032. UINT FAR* lpwOpenError
  1033. )
  1034. {
  1035. LPSTR lpFirstCommandItem, lpCurrentCommandItem;
  1036. UINT wArgumentPosition, wErr, wLen, wID, wDefaultID;
  1037. DWORD dwValue, dwDefaultValue;
  1038. BOOL bOpenCommand;
  1039. LPSTR FAR *lpstrPointerList;
  1040. UINT wPointers = 0;
  1041. UINT wHeaderSize = 0;
  1042. LPSTR lpDefaultCommandItem = NULL;
  1043. UINT wDefaultArgumentPosition;
  1044. if (lpwOpenError != NULL)
  1045. *lpwOpenError = 0;
  1046. // If the parameter pointer is NULL, return
  1047. if (lpstrParams == NULL)
  1048. {
  1049. DOUT ("Warning: lpstrParams is null in mciParseParams()\r\n");
  1050. return 0;
  1051. }
  1052. if ((lpstrPointerList =
  1053. mciAlloc ((MCI_MAX_PARAM_SLOTS + 1) * sizeof (LPSTR)))
  1054. == NULL)
  1055. {
  1056. *lpPointerList = NULL;
  1057. return MCIERR_OUT_OF_MEMORY;
  1058. }
  1059. // If this is the "open" command then allow parameter errors
  1060. bOpenCommand = lstrcmpi (lpCommandList, szOpen) == 0;
  1061. /* Clear all the flags */
  1062. *lpdwFlags = 0;
  1063. /* Initialize the entry for the callback message window handle */
  1064. wHeaderSize += sizeof (DWORD);
  1065. if (wHeaderSize > wParamsSize)
  1066. {
  1067. wErr = MCIERR_PARAM_OVERFLOW;
  1068. goto error_exit;
  1069. }
  1070. /* Skip past the header */
  1071. lpFirstCommandItem = (LPSTR)lpCommandList +
  1072. mciEatCommandEntry (lpCommandList, NULL, NULL);
  1073. wLen = mciEatCommandEntry (lpFirstCommandItem, &dwValue, &wID);
  1074. /* Make room in lpdwOutputParams for the return arguments if any */
  1075. if (wID == MCI_RETURN)
  1076. {
  1077. lpFirstCommandItem += wLen;
  1078. wHeaderSize += mciGetParamSize (dwValue, wID);
  1079. if (wHeaderSize > wParamsSize)
  1080. {
  1081. wErr = MCIERR_PARAM_OVERFLOW;
  1082. goto error_exit;
  1083. }
  1084. }
  1085. lpOutputParams += wHeaderSize;
  1086. // Scan the parameter string looking up each parameter in the given command
  1087. // list
  1088. while (TRUE)
  1089. {
  1090. LPCSTR lpstrArgument = NULL;
  1091. /* Remove leading blanks */
  1092. while (*lpstrParams == ' ') ++lpstrParams;
  1093. /* Break at end of parameter string */
  1094. if (*lpstrParams == '\0') break;
  1095. /* Scan for this parameter in the command list */
  1096. lpCurrentCommandItem = lpFirstCommandItem;
  1097. wLen = mciEatCommandEntry (lpCurrentCommandItem, &dwValue, &wID);
  1098. wArgumentPosition = 0;
  1099. /* While there are more tokens in the Command List */
  1100. while (wID != MCI_END_COMMAND)
  1101. {
  1102. /* Check for a default argument if not already read */
  1103. if (lpDefaultCommandItem == NULL &&
  1104. *lpCurrentCommandItem == '\0')
  1105. {
  1106. // Remember default argument
  1107. lpDefaultCommandItem = lpCurrentCommandItem;
  1108. dwDefaultValue = dwValue;
  1109. wDefaultID = wID;
  1110. wDefaultArgumentPosition = wArgumentPosition;
  1111. // break;
  1112. }
  1113. /* Check to see if this token matches */
  1114. else if ((lpstrArgument =
  1115. mciCheckToken (lpCurrentCommandItem, lpstrParams)) != NULL)
  1116. break;
  1117. /* This token did not match the input but advance the argument position */
  1118. wArgumentPosition += mciGetParamSize (dwValue, wID);
  1119. /* Go to next token */
  1120. lpCurrentCommandItem += wLen;
  1121. // Is this command parameter a constant?
  1122. if (wID == MCI_CONSTANT)
  1123. // Skip constant list
  1124. do
  1125. lpCurrentCommandItem +=
  1126. mciEatCommandEntry (lpCurrentCommandItem, &dwValue, &wID);
  1127. while (wID != MCI_END_CONSTANT);
  1128. wLen = mciEatCommandEntry (lpCurrentCommandItem, &dwValue, &wID);
  1129. } /* while */
  1130. /* If there were no matches */
  1131. if (lpstrArgument == NULL)
  1132. {
  1133. // If a default argument exists then try it
  1134. if (lpDefaultCommandItem != NULL)
  1135. {
  1136. lpstrArgument = lpstrParams;
  1137. dwValue = dwDefaultValue;
  1138. wID = wDefaultID;
  1139. lpCurrentCommandItem = lpDefaultCommandItem;
  1140. wArgumentPosition = wDefaultArgumentPosition;
  1141. } else
  1142. {
  1143. // Allow missing paramters on OPEN command if indicated by a non-null
  1144. // lpwOpenError address
  1145. if (!bOpenCommand || lpwOpenError == NULL)
  1146. {
  1147. wErr = MCIERR_UNRECOGNIZED_KEYWORD;
  1148. goto error_exit;
  1149. } else
  1150. {
  1151. // Skip the parameter if OPEN command
  1152. while (*lpstrParams != ' ' && *lpstrParams != '\0')
  1153. ++lpstrParams;
  1154. if (lpwOpenError != NULL)
  1155. *lpwOpenError = MCIERR_UNRECOGNIZED_KEYWORD;
  1156. continue;
  1157. }
  1158. }
  1159. }
  1160. /* Is there room in the output buffer for this argument? */
  1161. if (wArgumentPosition + wHeaderSize + mciGetParamSize (dwValue, wID)
  1162. > wParamsSize)
  1163. {
  1164. DOUT ("mciParseParams: parameter space overflow\r\n");
  1165. wErr = MCIERR_PARAM_OVERFLOW;
  1166. goto error_exit;
  1167. }
  1168. // Remove leading blanks
  1169. while (*lpstrArgument == ' ') ++lpstrArgument;
  1170. /* Process this parameter, filling in any flags or arguments */
  1171. if ((wErr = mciParseArgument (dwValue, wID,
  1172. &lpstrArgument, lpdwFlags,
  1173. &lpOutputParams[wArgumentPosition],
  1174. lpCurrentCommandItem))
  1175. != 0)
  1176. goto error_exit;
  1177. lpstrParams = lpstrArgument;
  1178. if (wID == MCI_STRING)
  1179. {
  1180. if (wPointers >= MCI_MAX_PARAM_SLOTS)
  1181. {
  1182. DOUT ("Warning: Out of pointer list slots in mciParseParams\r\n");
  1183. break;
  1184. }
  1185. (DWORD)lpstrPointerList[wPointers++] =
  1186. *(LPDWORD)&lpOutputParams[wArgumentPosition];
  1187. }
  1188. /* Continue reading the parameter string */
  1189. } /* while */
  1190. // Terminate list
  1191. lpstrPointerList[wPointers] = NULL;
  1192. // Copy reference for caller
  1193. *lpPointerList = lpstrPointerList;
  1194. // Return Success
  1195. return 0;
  1196. error_exit:
  1197. *lpPointerList = NULL;
  1198. // Terminate list
  1199. lpstrPointerList[wPointers] = NULL;
  1200. mciParserFree (lpstrPointerList);
  1201. return wErr;
  1202. }
  1203. /*
  1204. * @doc INTERNAL MCI
  1205. * @func UINT | mciParseCommand | This function converts an MCI
  1206. * control string to an MCI control message suitable for sending to
  1207. * <f mciSendCommand>. The input string usually comes from <f mciSendString>
  1208. * and always has the device name stripped off the front.
  1209. *
  1210. * @parm UINT | wDeviceID | Identifies the device. First searches the parsing
  1211. * table belonging to the driver.
  1212. * Then searches the command tables matching the type
  1213. * of the given device. Then searches the core command table.
  1214. *
  1215. * @parm LPSTR | lpstrCommand | An MCI control command without
  1216. * a device name prefix. There must be no leading or trailing
  1217. * blanks.
  1218. *
  1219. * @parm LPCSTR | lpstrDeviceName | The device name (second token on the
  1220. * command line). It is used to identify the device type.
  1221. *
  1222. * @parm LPSTR FAR * | lpCommandList | If not NULL then the address of
  1223. * the command list for the parsed command (if successful) is copied here.
  1224. * It is used later by mciSendString when parsing arguments
  1225. *
  1226. * @parm UINT FAR* | lpwTable | The table resource ID to be unlocked
  1227. * after parsing. Returned to caller.
  1228. *
  1229. * @rdesc Returns the command ID or 0 if not found.
  1230. *
  1231. */
  1232. UINT PASCAL NEAR
  1233. mciParseCommand(
  1234. UINT wDeviceID,
  1235. LPSTR lpstrCommand,
  1236. LPCSTR lpstrDeviceName,
  1237. LPSTR FAR * lpCommandList,
  1238. UINT FAR* lpwTable)
  1239. {
  1240. LPSTR lpCommandItem;
  1241. UINT wMessage;
  1242. // Put the command in lower case
  1243. //!! mciToLower (lpstrCommand);
  1244. // Look up lpstrCommand in the parser's command tables.
  1245. if ((lpCommandItem = FindCommandItem (wDeviceID, lpstrDeviceName,
  1246. lpstrCommand,
  1247. &wMessage, lpwTable))
  1248. == NULL)
  1249. return 0;
  1250. /* Return the command list to the caller */
  1251. if (lpCommandList != NULL)
  1252. *lpCommandList = lpCommandItem;
  1253. else
  1254. DOUT ("Warning: NULL lpCommandList in mciParseCommanad\r\n");
  1255. return wMessage;
  1256. }
  1257. /*
  1258. * @doc INTERNAL MCI
  1259. * @func void | mciParserFree | Free any buffers allocated to
  1260. * receive string arguments.
  1261. *
  1262. * @parm LPSTR FAR * | lpstrPointerList | A NULL terminated list of far
  1263. * pointers to strings to be free'd
  1264. *
  1265. */
  1266. void PASCAL NEAR
  1267. mciParserFree(
  1268. LPSTR FAR *lpstrPointerList
  1269. )
  1270. {
  1271. LPSTR FAR *lpstrOriginal = lpstrPointerList;
  1272. if (lpstrPointerList == NULL)
  1273. return;
  1274. while (*lpstrPointerList != NULL)
  1275. mciFree (*lpstrPointerList++);
  1276. mciFree (lpstrOriginal);
  1277. }