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.

1042 lines
24 KiB

  1. /*++
  2. Copyright (C) Microsoft Corporation, 1997 - 1999
  3. All rights reserved.
  4. Module Name:
  5. parse.cxx
  6. Abstract:
  7. Command line parser.
  8. Author:
  9. Steve Kiraly (SteveKi) 04-Mar-1997
  10. Revision History:
  11. --*/
  12. #include "precomp.hxx"
  13. #pragma hdrstop
  14. #include "parser.hxx"
  15. /*++
  16. Routine Name:
  17. Memory allocations.
  18. Routine Description:
  19. Uses new and delete when a C++ file.
  20. Arguments:
  21. Normal allocation routines.
  22. Return Value:
  23. N/A
  24. --*/
  25. static inline PVOID private_alloc( UINT size )
  26. {
  27. return new BYTE [ size ];
  28. }
  29. static inline VOID private_free( PVOID pvoid )
  30. {
  31. delete [] pvoid;
  32. }
  33. /*++
  34. Routine Name:
  35. StringToArgv
  36. Routine Description:
  37. Coverts a commandline string to an array of pointers
  38. to string.
  39. Arguments:
  40. TCHAR *string - pointer to command line string.
  41. TCHAR *pac - pointer to an int on return will contain the
  42. number of strins in the arrary of pointes.
  43. TCHAR **string - pointer to where to return a pointer to the
  44. array of pointers to strings.
  45. Return Value:
  46. TRUE if success, FALSE error occurred.
  47. Notes:
  48. --*/
  49. BOOL
  50. StringToArgv(
  51. const TCHAR *str,
  52. UINT *pac,
  53. TCHAR ***pppav,
  54. BOOL bParseExeName
  55. )
  56. {
  57. TCHAR *word = NULL;
  58. TCHAR *w = NULL;
  59. TCHAR *program_name = NULL;
  60. TCHAR **av = NULL;
  61. LPTSTR string = NULL;
  62. UINT ac = 0;
  63. UINT numslash;
  64. BOOL inquote;
  65. BOOL copychar;
  66. BOOL retval = TRUE;
  67. TString strString;
  68. //
  69. // The pointers passed in must be valid.
  70. //
  71. if( !str || !pac || !pppav )
  72. {
  73. SPLASSERT( FALSE );
  74. return FALSE;
  75. }
  76. //
  77. // Process any file redirection.
  78. //
  79. vProcessCommandFileRedirection( str, strString );
  80. //
  81. // Cast the string to a usable string pointer.
  82. //
  83. string = const_cast<LPTSTR>( static_cast<LPCTSTR>( strString ) );
  84. //
  85. // Allocate the word buffer, this the maximum size in case there is
  86. // just one extreamly long argument.
  87. //
  88. word = (LPTSTR) private_alloc( sizeof (TCHAR) * (lstrlen (string) + 1) );
  89. if( word )
  90. {
  91. //
  92. // If we are to get the parse the program name from the provided command line.
  93. //
  94. if( bParseExeName )
  95. {
  96. retval = IsolateProgramName( string, word, &string );
  97. //
  98. // If program name found.
  99. //
  100. if( retval )
  101. {
  102. //
  103. // Add the program name to the argv list.
  104. //
  105. retval = AddStringToArgv( &av, word );
  106. if( retval )
  107. {
  108. ac++;
  109. }
  110. }
  111. }
  112. }
  113. else
  114. {
  115. retval = FALSE;
  116. }
  117. for ( ; retval && *string; ac++ )
  118. {
  119. //
  120. // Skip any leading spaces.
  121. //
  122. for (; *string && _istspace(*string); string++);
  123. //
  124. // Get a word out of the string
  125. //
  126. for (copychar = 1, inquote = 0, w = word; *string; copychar = 1)
  127. {
  128. //
  129. // Rules: 2N backslashes + " ==> N backslashes and begin/end quote
  130. // 2N+1 backslashes + " ==> N backslashes + literal "
  131. // N backslashes ==> N backslashes
  132. //
  133. for( numslash = 0; *string == TEXT('\\'); string++ )
  134. {
  135. //
  136. // count number of backslashes for use below
  137. //
  138. numslash++;
  139. }
  140. if (*string == TEXT('"'))
  141. {
  142. //
  143. // if 2N backslashes before, start/end quote, otherwise
  144. // copy literally
  145. //
  146. if (numslash % 2 == 0)
  147. {
  148. if (inquote)
  149. {
  150. //
  151. // Double quote inside quoted string
  152. // skip first quote char and copy second
  153. //
  154. if (*(string+1) == TEXT('"') )
  155. {
  156. string++;
  157. }
  158. else
  159. {
  160. copychar = 0;
  161. }
  162. }
  163. else
  164. {
  165. //
  166. // don't copy quote
  167. //
  168. copychar = 0;
  169. }
  170. inquote = !inquote;
  171. }
  172. //
  173. // Divide numslash by two
  174. //
  175. numslash /= 2;
  176. }
  177. //
  178. // Copy the slashes
  179. //
  180. for( ; numslash; numslash--)
  181. {
  182. *w++ = TEXT('\\');
  183. }
  184. //
  185. // If at the end of the command line or
  186. // a space has been found.
  187. //
  188. if( !*string || (!inquote && _istspace(*string)))
  189. {
  190. break;
  191. }
  192. //
  193. // copy character into argument
  194. //
  195. if (copychar)
  196. {
  197. *w++ = *string;
  198. }
  199. string++;
  200. }
  201. //
  202. // Terminate the word
  203. //
  204. *w = 0;
  205. retval = AddStringToArgv( &av, word );
  206. }
  207. //
  208. // Free up the word buffer.
  209. //
  210. if( word )
  211. {
  212. private_free( word );
  213. }
  214. if( retval )
  215. {
  216. *pac = ac;
  217. *pppav = av;
  218. }
  219. #if DBG
  220. vDumpArgList( ac, av );
  221. #endif
  222. return retval;
  223. }
  224. /*++
  225. Routine Name:
  226. ReleaseArgv
  227. Routine Description:
  228. Releases the argv pointer to an array of pointers to
  229. strings.
  230. Arguments:
  231. TCHAR **av - pointer to an arrary of pointers to strings.
  232. Return Value:
  233. Nothing.
  234. Notes:
  235. This routine releases the strings as well as the arrary of
  236. pointers to strings.
  237. --*/
  238. VOID
  239. ReleaseArgv(
  240. TCHAR **av
  241. )
  242. {
  243. TCHAR **tav;
  244. if( av )
  245. {
  246. for( tav = av; *tav; tav++ )
  247. {
  248. if( *tav )
  249. {
  250. private_free( *tav );
  251. }
  252. }
  253. private_free( av );
  254. }
  255. }
  256. /*++
  257. Routine Name:
  258. IsolateProgramName
  259. Routine Description:
  260. Extracts the program name from the specified string.
  261. This routing is used because the program name follows
  262. a differenct parsing technique from the other command
  263. line arguments.
  264. Arguments:
  265. TCHAR *p - pointer to a string which contains the program name.
  266. TCHAR *program_name - pointer buffer where to place the extracted
  267. program.
  268. TCHAR **string - pointer to where to return a pointer where the
  269. program name ended. Used as the start of the string
  270. for nay remaing command line arguments.
  271. Return Value:
  272. TRUE if success, FALSE error occurred.
  273. Notes:
  274. This routine is very specific to the NTFS file nameing
  275. format.
  276. --*/
  277. BOOL
  278. IsolateProgramName(
  279. const TCHAR *p,
  280. TCHAR *program_name,
  281. TCHAR **string
  282. )
  283. {
  284. BOOL retval = FALSE;
  285. TCHAR c;
  286. //
  287. // All the arguments must be valid.
  288. //
  289. if( p && program_name && *string )
  290. {
  291. retval = TRUE;
  292. }
  293. //
  294. // Skip any leading spaces.
  295. //
  296. for( ; retval && *p && _istspace(*p); p++ );
  297. //
  298. // A quoted program name is handled here. The handling is much
  299. // simpler than for other arguments. Basically, whatever lies
  300. // between the leading double-quote and next one, or a terminal null
  301. // character is simply accepted. Fancier handling is not required
  302. // because the program name must be a legal NTFS/HPFS file name.
  303. //
  304. if (retval && *p == TEXT('"'))
  305. {
  306. //
  307. // scan from just past the first double-quote through the next
  308. // double-quote, or up to a null, whichever comes first
  309. //
  310. while ((*(++p) != TEXT('"')) && (*p != TEXT('\0')))
  311. {
  312. *program_name++ = *p;
  313. }
  314. //
  315. // append the terminating null
  316. //
  317. *program_name++ = TEXT('\0');
  318. //
  319. // if we stopped on a double-quote (usual case), skip over it
  320. //
  321. if (*p == TEXT('"'))
  322. {
  323. p++;
  324. }
  325. }
  326. else
  327. {
  328. //
  329. // Not a quoted program name
  330. //
  331. do {
  332. *program_name++ = *p;
  333. c = (TCHAR) *p++;
  334. } while (c > TEXT(' '));
  335. if (c == TEXT('\0'))
  336. {
  337. p--;
  338. }
  339. else
  340. {
  341. *(program_name - 1) = TEXT('\0');
  342. }
  343. }
  344. if( retval )
  345. {
  346. *string = const_cast<TCHAR *>( p );
  347. }
  348. return retval;
  349. }
  350. /*++
  351. Routine Name:
  352. AddStringToArgv
  353. Routine Description:
  354. Adds a string to a array of pointers to strings.
  355. Arguments:
  356. TCHAR ***argv - pointer where to return the new array
  357. of pointer to strings.
  358. TCHAR *word - pointer to word or string to add to the array.
  359. Return Value:
  360. TRUE if success, FALSE error occurred.
  361. Notes:
  362. Realloc is not used here because a user of this code
  363. may decide to do allocations with new and delete rather
  364. that malloc free. Both the new array pointer and string
  365. pointer must be valid for this routine to do anything.
  366. The array pointer can point to null but the pointer cannot
  367. be null.
  368. --*/
  369. BOOL
  370. AddStringToArgv(
  371. TCHAR ***argv,
  372. TCHAR *word
  373. )
  374. {
  375. BOOL retval = FALSE;
  376. UINT count;
  377. TCHAR **targv = NULL;
  378. TCHAR **tmp = NULL;
  379. TCHAR **nargv = NULL;
  380. TCHAR *w = NULL;
  381. if( argv && word )
  382. {
  383. //
  384. // Allocate the word buffer plus the null.
  385. //
  386. w = (TCHAR *)private_alloc( ( lstrlen( word ) + 1 ) * sizeof(TCHAR) );
  387. if( w )
  388. {
  389. //
  390. // Copy the word.
  391. //
  392. lstrcpy( w, word );
  393. //
  394. // Count the current size of the argv.
  395. //
  396. for( count = 1, targv = *argv; targv && *targv; targv++, count++ );
  397. nargv = (TCHAR **)private_alloc( ( count + 1 ) * sizeof(TCHAR *) );
  398. if( nargv )
  399. {
  400. //
  401. // Copy the orig argv to the new argv.
  402. //
  403. for( tmp = nargv, targv = *argv; targv && *targv; *nargv++ = *targv++ );
  404. //
  405. // Set the new string pointer.
  406. //
  407. *nargv++ = w;
  408. //
  409. // Mark the end.
  410. //
  411. *nargv = 0;
  412. //
  413. // Free the original argv
  414. //
  415. private_free( *argv );
  416. //
  417. // Set the return pointer.
  418. //
  419. *argv = tmp;
  420. retval = TRUE;
  421. }
  422. }
  423. }
  424. if( !retval )
  425. {
  426. if( w )
  427. private_free(w);
  428. if( nargv )
  429. private_free(nargv);
  430. }
  431. return retval;
  432. }
  433. /********************************************************************
  434. Code for commad file redirection.
  435. ********************************************************************/
  436. /*++
  437. Routine Name:
  438. vProcessCommandFileRedirection
  439. Routine Description:
  440. Arguments:
  441. Return Value:
  442. Nothing.
  443. --*/
  444. VOID
  445. vProcessCommandFileRedirection(
  446. IN LPCTSTR pszCmdLine,
  447. IN OUT TString &strCmdLine
  448. )
  449. {
  450. DBGMSG( DBG_TRACE, ( "vProcessCommandFileRedirection\n" ) );
  451. DBGMSG( DBG_TRACE, ( "Pre vProcessCommandFileRedirection "TSTR"\n", pszCmdLine ) );
  452. SPLASSERT( pszCmdLine );
  453. TCHAR szFilename [MAX_PATH];
  454. TCHAR szBuffer [MAX_PATH];
  455. LPTSTR pSrc = const_cast<LPTSTR>( pszCmdLine );
  456. LPTSTR pDst = szBuffer;
  457. UINT nBufferSize = COUNTOF( szBuffer );
  458. TStatusB bStatus;
  459. for( ; pSrc && *pSrc; )
  460. {
  461. //
  462. // Look for the escape character. If found - look for
  463. // the special characters.
  464. //
  465. if( *pSrc == TEXT('\\') && *(pSrc+1) == TEXT('@') )
  466. {
  467. //
  468. // Copy the special character skipping the escape.
  469. //
  470. *pDst++ = *(pSrc+1);
  471. //
  472. // Skip two characters in the source buffer
  473. //
  474. pSrc += 2;
  475. //
  476. // Flush the buffer if it's full.
  477. //
  478. bStatus DBGCHK = bFlushBuffer( szBuffer, nBufferSize, &pDst, strCmdLine, FALSE );
  479. }
  480. //
  481. // Look for an redirected file sentinal.
  482. //
  483. else if( *pSrc == TEXT('@') )
  484. {
  485. //
  486. // Flush the current working buffer.
  487. //
  488. bStatus DBGCHK = bFlushBuffer( szBuffer, nBufferSize, &pDst, strCmdLine, TRUE );
  489. //
  490. // Isolate the file name following the sentinal, note this
  491. // requires special parsing for NTFS file names.
  492. //
  493. if( IsolateProgramName( pSrc+1, szFilename, &pSrc ) )
  494. {
  495. AFileInfo FileInfo;
  496. //
  497. // Read the file into the a single buffer.
  498. //
  499. if( bReadCommandFileRedirection( szFilename, &FileInfo ) )
  500. {
  501. //
  502. // Tack on the file information to the command line.
  503. //
  504. bStatus DBGCHK = strCmdLine.bCat( FileInfo.pszOffset );
  505. }
  506. //
  507. // Release the file information.
  508. //
  509. if( FileInfo.pData )
  510. {
  511. private_free( FileInfo.pData );
  512. }
  513. }
  514. }
  515. else
  516. {
  517. //
  518. // Copy the string to working buffer.
  519. //
  520. *pDst++ = *pSrc++;
  521. //
  522. // Flush the buffer if it's full.
  523. //
  524. bStatus DBGCHK = bFlushBuffer( szBuffer, nBufferSize, &pDst, strCmdLine, FALSE );
  525. }
  526. }
  527. //
  528. // Flush any remaining items into the command line string.
  529. //
  530. bStatus DBGCHK = bFlushBuffer( szBuffer, nBufferSize, &pDst, strCmdLine, TRUE );
  531. DBGMSG( DBG_TRACE, ( "Post vProcessCommandFileRedirection "TSTR"\n", (LPCTSTR)strCmdLine ) );
  532. }
  533. /*++
  534. Routine Name:
  535. bFlushBuffer
  536. Routine Description:
  537. Flushed the working buffer for builing the complete
  538. redirected command string.
  539. Arguments:
  540. pszBuffer - Pointer to working buffer.
  541. nSize - Total size in bytes of working buffer.
  542. pszCurrent - Current pointer into working buffer,
  543. strDestination - Reference to destination string buffer.
  544. bForceFlush - TRUE force buffer flush, FALSE flush if full.
  545. Return Value:
  546. TRUE buffer flushed successfully, FALSE error occurred.
  547. --*/
  548. BOOL
  549. bFlushBuffer(
  550. IN LPCTSTR pszBuffer,
  551. IN UINT nSize,
  552. IN OUT LPTSTR *pszCurrent,
  553. IN OUT TString &strDestination,
  554. IN BOOL bForceFlush
  555. )
  556. {
  557. TStatusB bStatus;
  558. bStatus DBGNOCHK = TRUE;
  559. //
  560. // If something is in the buffer.
  561. //
  562. if( *pszCurrent != pszBuffer )
  563. {
  564. //
  565. // If the destination buffer is full.
  566. // nsize - 2 because the pointers are zero base and the
  567. // we need one extra slot for the null terminator.
  568. //
  569. if( ( *pszCurrent > ( pszBuffer + nSize - 2 ) ) || bForceFlush )
  570. {
  571. //
  572. // Null terminate the working buffer.
  573. //
  574. **pszCurrent = 0;
  575. //
  576. // Tack on the string to the destination string.
  577. //
  578. bStatus DBGCHK = strDestination.bCat( pszBuffer );
  579. //
  580. // Reset the current buffer pointer.
  581. //
  582. *pszCurrent = const_cast<LPTSTR>( pszBuffer );
  583. }
  584. }
  585. return bStatus;
  586. }
  587. /*++
  588. Routine Name:
  589. bReadCommandFileRedirection
  590. Routine Description:
  591. Read the filename into a single string. This in my
  592. opinion is a hack, but does the job. I made the assumption
  593. the commad file will always be less than 16k in size.
  594. Because the file size assumption was made it became feasable
  595. to just read the file into one potentialy huge buffer. Note
  596. the file is not broken up into separate lines the new lines are
  597. modified inplace to spaces. The StringToArgv will parse the
  598. line into an argv list ignoring spaces where appropriate.
  599. Arguments:
  600. szFilename - pointer to the redirected filename.
  601. pFileInfo - pointe to file information structure.
  602. Return Value:
  603. TRUE function complete ok. FALSE error occurred.
  604. --*/
  605. BOOL
  606. bReadCommandFileRedirection(
  607. IN LPCTSTR szFilename,
  608. IN AFileInfo *pFileInfo
  609. )
  610. {
  611. DBGMSG( DBG_TRACE, ( "bReadCommandFileRedirection\n" ) );
  612. DBGMSG( DBG_TRACE, ( "szFilename "TSTR"\n", szFilename ) );
  613. HANDLE hFile = INVALID_HANDLE_VALUE;
  614. BOOL bReturn = FALSE;
  615. DWORD dwBytesRead = 0;
  616. DWORD dwFileSize = 0;
  617. LPTSTR pszUnicode = NULL;
  618. #ifdef UNICODE
  619. enum { kByteOrderMark = 0xFEFF,
  620. kMaxFileSize = 16*1024 };
  621. memset( pFileInfo, 0, sizeof( AFileInfo ) );
  622. //
  623. // Open the redirected file.
  624. //
  625. hFile = CreateFile ( szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
  626. if( hFile != INVALID_HANDLE_VALUE )
  627. {
  628. //
  629. // Validate the file size.
  630. //
  631. dwFileSize = GetFileSize( hFile, NULL );
  632. //
  633. // We fail the call if the file is bigger than a reasonable
  634. // file size. This was a decision made to make
  635. // reading the file into a contiguous buffer a reasonable
  636. // approach.
  637. //
  638. if( dwFileSize <= kMaxFileSize )
  639. {
  640. //
  641. // Allocate buffer to hold entire file plus a null terminator.
  642. //
  643. pFileInfo->pData = (PVOID)private_alloc( dwFileSize + sizeof( TCHAR ) );
  644. if( pFileInfo->pData )
  645. {
  646. //
  647. // Read the file into one huge buffer.
  648. //
  649. bReturn = ReadFile( hFile, pFileInfo->pData, dwFileSize, &dwBytesRead, NULL );
  650. //
  651. // If the read succeeded.
  652. //
  653. if( bReturn && dwBytesRead == dwFileSize )
  654. {
  655. //
  656. // Assume failure.
  657. //
  658. bReturn = FALSE;
  659. //
  660. // Check for the byte order mark, This mark allows use to
  661. // read both ansi and unicode file.
  662. //
  663. if( *(LPWORD)pFileInfo->pData != kByteOrderMark )
  664. {
  665. DBGMSG( DBG_TRACE, ( "Ansi file found\n" ) );
  666. //
  667. // Null terminate the file.
  668. //
  669. *((LPBYTE)pFileInfo->pData + dwBytesRead) = 0;
  670. pszUnicode = (LPTSTR)private_alloc( ( strlen( (LPSTR)pFileInfo->pData ) + 1 ) * sizeof(TCHAR) );
  671. if( pszUnicode )
  672. {
  673. if( AnsiToUnicodeString( (LPSTR)pFileInfo->pData, pszUnicode, 0 ) )
  674. {
  675. //
  676. // Release the ansi string, and assign the unicode string.
  677. // then set the offset.
  678. //
  679. private_free( pFileInfo->pData );
  680. pFileInfo->pData = pszUnicode;
  681. pFileInfo->pszOffset = pszUnicode;
  682. bReturn = TRUE;
  683. }
  684. }
  685. }
  686. else
  687. {
  688. DBGMSG( DBG_TRACE, ( "Unicode file found\n" ) );
  689. //
  690. // Null terminate file string.
  691. //
  692. *((LPBYTE)pFileInfo->pData + dwBytesRead) = 0;
  693. *((LPBYTE)pFileInfo->pData + dwBytesRead+1) = 0;
  694. //
  695. // Set the file offset to just after the byte mark.
  696. //
  697. pFileInfo->pszOffset = (LPTSTR)((LPBYTE)pFileInfo->pData + sizeof( WORD ) );
  698. bReturn = TRUE;
  699. }
  700. if( bReturn )
  701. {
  702. //
  703. // Replace carriage returns and line feeds with spaces.
  704. // The spaces will be removed when the command line is
  705. // converted to an argv list.
  706. //
  707. vReplaceNewLines( pFileInfo->pszOffset );
  708. }
  709. }
  710. }
  711. }
  712. else
  713. {
  714. DBGMSG( DBG_WARN, ( "Redirected file too large %d.\n", dwFileSize ) );
  715. }
  716. CloseHandle( hFile );
  717. }
  718. //
  719. // If something failed release all allocated resources.
  720. //
  721. if( !bReturn )
  722. {
  723. private_free( pFileInfo->pData );
  724. private_free( pszUnicode );
  725. pFileInfo->pData = NULL;
  726. pFileInfo->pszOffset = NULL;
  727. }
  728. #endif
  729. return bReturn;
  730. }
  731. /*++
  732. Routine Name:
  733. vReplaseCarriageReturnAndLineFeed
  734. Routine Description:
  735. Replace carriage returns and line feeds with spaces.
  736. Arguments:
  737. pszLine - pointer to line buffer where to replace carriage
  738. returns and line feed characters.
  739. Return Value:
  740. Nothing.
  741. --*/
  742. VOID
  743. vReplaceNewLines(
  744. IN LPTSTR pszLine
  745. )
  746. {
  747. DBGMSG( DBG_TRACE, ( "vReplaceNewLines\n" ) );
  748. for( LPTSTR p = pszLine; p && *p; ++p )
  749. {
  750. if( *p == TEXT('\n') || *p == TEXT('\r') )
  751. {
  752. *p = TEXT(' ');
  753. }
  754. }
  755. }
  756. /*++
  757. Routine Name:
  758. AnsiToUnicodeString
  759. Routine Description:
  760. Converts an ansi string to unicode.
  761. Arguments:
  762. pAnsi - Ansi string to convert.
  763. pUnicode - Unicode buffer where to place the converted string.
  764. StringLength - Ansi string argument length, this may be null
  765. then length will be calculated.
  766. Return Value:
  767. Return value from MultiByteToWideChar.
  768. --*/
  769. INT
  770. AnsiToUnicodeString(
  771. IN LPSTR pAnsi,
  772. IN LPWSTR pUnicode,
  773. IN DWORD StringLength OPTIONAL
  774. )
  775. {
  776. INT iReturn;
  777. if( StringLength == 0 )
  778. {
  779. StringLength = strlen( pAnsi );
  780. }
  781. iReturn = MultiByteToWideChar(CP_ACP,
  782. MB_PRECOMPOSED,
  783. pAnsi,
  784. -1,
  785. pUnicode,
  786. StringLength + 1 );
  787. //
  788. // Ensure NULL termination.
  789. //
  790. pUnicode[StringLength] = 0;
  791. return iReturn;
  792. }
  793. /*++
  794. Routine Name:
  795. vDumpArgList
  796. Routine Description:
  797. Dumps the arg list to the debugger.
  798. Arguments:
  799. ac - Count of strings in the argv list.
  800. av - Pointer to an array of pointers to strings,
  801. which I call an argv list.
  802. Return Value:
  803. Return value from MultiByteToWideChar.
  804. --*/
  805. #if DBG
  806. VOID
  807. vDumpArgList(
  808. UINT ac,
  809. TCHAR **av
  810. )
  811. {
  812. DBGMSG( DBG_TRACE, ( "vDumpArgList\n" ) );
  813. for( UINT i = 0; ac; --ac, ++av, ++i )
  814. {
  815. DBGMSG( DBG_TRACE, ( "%d - "TSTR"\n", i, *av ) );
  816. }
  817. }
  818. #endif