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.

791 lines
22 KiB

  1. /****************************************************************************
  2. debug.c
  3. winmm debug support module
  4. Copyright (c) 1990-2001 Microsoft Corporation
  5. History
  6. 10/1/92 Updated for NT by Robin Speed (RobinSp)
  7. ****************************************************************************/
  8. #include "nt.h"
  9. #include "ntrtl.h"
  10. #include "nturtl.h"
  11. #include "winmmi.h"
  12. #include <wchar.h>
  13. #include <stdarg.h>
  14. // no REAL logging for now ! - NT doesn't have Dr Watson !
  15. #define LogParamError(a, b)
  16. RTL_RESOURCE gHandleListResource;
  17. /***************************************************************************
  18. * @doc INTERNAL
  19. *
  20. * @func HANDLE | NewHandle | allocate a fixed handle in MMSYSTEM's local heap
  21. *
  22. * @parm UINT | uType | unique id describing handle type
  23. * @parm UINT | uSize | size in bytes to be allocated
  24. *
  25. * @rdesc Returns pointer/handle to memory object
  26. *
  27. * @comm a standard handle header (HNDL) will be added to the object,
  28. * and it will be linked into the list of MMSYSTEM handles.
  29. *
  30. ***************************************************************************/
  31. HANDLE NewHandle(UINT uType, PCWSTR cookie, UINT uSize)
  32. {
  33. PHNDL pHandle;
  34. pHandle = (PHNDL)HeapAlloc(hHeap, 0, sizeof(HNDL) + uSize);
  35. if (pHandle == NULL) {
  36. return pHandle;
  37. } else {
  38. ZeroMemory(pHandle, sizeof(HNDL) + uSize); // zero the whole bludy lot
  39. if (!mmInitializeCriticalSection(&pHandle->CritSec)) {
  40. HeapFree(hHeap, 0, (LPSTR)pHandle);
  41. return NULL;
  42. }
  43. pHandle->hThread = GetCurrentTask(); // For WOW validation
  44. pHandle->uType = uType;
  45. pHandle->cookie = cookie;
  46. RtlAcquireResourceExclusive(&gHandleListResource, TRUE);
  47. EnterCriticalSection(&HandleListCritSec);
  48. pHandle->pNext = pHandleList;
  49. pHandleList = pHandle;
  50. LeaveCriticalSection(&HandleListCritSec);
  51. }
  52. return PHtoH(pHandle);
  53. }
  54. void AcquireHandleListResourceShared()
  55. {
  56. RtlAcquireResourceShared(&gHandleListResource, TRUE);
  57. }
  58. void AcquireHandleListResourceExclusive()
  59. {
  60. RtlAcquireResourceExclusive(&gHandleListResource, TRUE);
  61. }
  62. void ReleaseHandleListResource()
  63. {
  64. RtlReleaseResource(&gHandleListResource);
  65. }
  66. /***************************************************************************
  67. * @doc INTERNAL
  68. *
  69. * @func HANDLE | FreeHandle | free handle allocated with NewHandle
  70. *
  71. * @parm HANDLE | hUser | handle returned from NewHandle
  72. *
  73. * @comm handle will be unlinked from list, and memory will be freed
  74. *
  75. *
  76. ***************************************************************************/
  77. void FreeHandle(HANDLE hUser)
  78. {
  79. /* Find handle and free from list */
  80. PHNDL pHandle;
  81. PHNDL *pSearch;
  82. if (hUser == NULL) {
  83. return;
  84. }
  85. //
  86. // Point to our handle data
  87. //
  88. pHandle = HtoPH(hUser);
  89. AcquireHandleListResourceExclusive();
  90. EnterCriticalSection(&HandleListCritSec);
  91. pSearch = &pHandleList;
  92. while (*pSearch != NULL) {
  93. if (*pSearch == pHandle) {
  94. //
  95. // Found it
  96. // Remove it from the list
  97. //
  98. *pSearch = pHandle->pNext;
  99. LeaveCriticalSection(&HandleListCritSec);
  100. // Making sure no one is using the handle while we mark it as invalid.
  101. EnterCriticalSection(&pHandle->CritSec);
  102. pHandle->uType = 0;
  103. pHandle->fdwHandle = 0L;
  104. pHandle->hThread = NULL;
  105. pHandle->pNext = NULL;
  106. LeaveCriticalSection(&pHandle->CritSec);
  107. DeleteCriticalSection(&pHandle->CritSec);
  108. HeapFree(hHeap, 0, (LPSTR)pHandle);
  109. ReleaseHandleListResource();
  110. return;
  111. } else {
  112. pSearch = &(*pSearch)->pNext;
  113. }
  114. }
  115. dprintf1(("Freeing handle which is not in the list !"));
  116. WinAssert(FALSE);
  117. LeaveCriticalSection(&HandleListCritSec);
  118. ReleaseHandleListResource();
  119. }
  120. /***************************************************************************
  121. * @doc INTERNAL
  122. *
  123. * @func HANDLE | InvalidateHandle | invalidate handle allocated with
  124. * NewHandle for parameter validation
  125. *
  126. * @parm HANDLE | hUser | handle returned from NewHandle
  127. *
  128. * @comm handle will be marked as TYPE_UNKNOWN, causing handle based API's to
  129. * fail.
  130. *
  131. *
  132. ***************************************************************************/
  133. void InvalidateHandle(HANDLE hUser)
  134. {
  135. /* Find handle and free from list */
  136. PHNDL pHandle;
  137. if (hUser == NULL) {
  138. return;
  139. }
  140. //
  141. // Point to our handle data
  142. //
  143. pHandle = HtoPH(hUser);
  144. pHandle->uType = TYPE_UNKNOWN;
  145. }
  146. /**************************************************************************
  147. @doc INTERNAL
  148. @api void | winmmSetDebugLevel | Set the current debug level
  149. @parm int | iLevel | The new level to set
  150. @rdesc There is no return value
  151. **************************************************************************/
  152. void winmmSetDebugLevel(int level)
  153. {
  154. #if DBG
  155. winmmDebugLevel = level;
  156. dprintf(("debug level set to %d", winmmDebugLevel));
  157. #endif
  158. }
  159. STATICDT UINT inited=0;
  160. #if DBG
  161. extern int mciDebugLevel;
  162. #endif
  163. #if DBG
  164. void InitDebugLevel(void)
  165. {
  166. if (!inited) {
  167. INT level;
  168. level = GetProfileInt("MMDEBUG", "WINMM", 99);
  169. if (level != 99) {
  170. winmmDebugLevel = level;
  171. }
  172. level = GetProfileInt("MMDEBUG", "MCI", 99);
  173. if (level != 99) {
  174. mciDebugLevel = level;
  175. }
  176. inited = 1;
  177. }
  178. dprintf2(("Starting, debug level=%d", winmmDebugLevel));
  179. }
  180. #endif
  181. #ifdef DEBUG_RETAIL
  182. /***************************************************************************
  183. * @doc INTERNAL WAVE MIDI
  184. *
  185. * @func BOOL | ValidateHeader | validates a wave or midi date header
  186. *
  187. * @parm LPVOID | lpHeader| pointer to wave/midi header
  188. * @parm UINT | wSize | size of header passed by app
  189. * @parm UINT | wType | unique id describing header/handle type
  190. *
  191. * @rdesc Returns TRUE if <p> is non NULL and <wSize> is the correct size
  192. * Returns FALSE otherwise
  193. *
  194. * @comm if the header is invalid an error will be generated.
  195. *
  196. ***************************************************************************/
  197. BOOL ValidateHeader(PVOID pHdr, UINT uSize, UINT uType)
  198. {
  199. // Detect bad header
  200. if (!ValidateWritePointer(pHdr, uSize)) {
  201. DebugErr(DBF_ERROR, "Invalid header pointer");
  202. return FALSE;
  203. }
  204. // Check type
  205. switch (uType) {
  206. case TYPE_WAVEOUT:
  207. case TYPE_WAVEIN:
  208. {
  209. PWAVEHDR pHeader = pHdr;
  210. // Check header
  211. if (uSize < sizeof(WAVEHDR)) {
  212. DebugErr(DBF_ERROR, "Invalid header size");
  213. LogParamError(ERR_BAD_VALUE, uSize);
  214. return FALSE;
  215. }
  216. if (pHeader->dwFlags & ~WHDR_VALID) {
  217. LogParamError(ERR_BAD_FLAGS, ((PWAVEHDR)pHeader)->dwFlags);
  218. return FALSE;
  219. }
  220. // Check buffer
  221. if (!(uType == TYPE_WAVEOUT
  222. ? ValidateReadPointer(pHeader->lpData, pHeader->dwBufferLength)
  223. : ValidateWritePointer(pHeader->lpData, pHeader->dwBufferLength))
  224. ) {
  225. DebugErr(DBF_ERROR, "Invalid buffer pointer");
  226. return FALSE;
  227. }
  228. }
  229. break;
  230. case TYPE_MIDIIN:
  231. case TYPE_MIDIOUT:
  232. case TYPE_MIDISTRM:
  233. {
  234. PMIDIHDR pHeader = pHdr;
  235. if ((TYPE_MIDISTRM == uType) &&
  236. uSize < sizeof(MIDIHDR))
  237. {
  238. DebugErr(DBF_ERROR, "Invalid header size");
  239. LogParamError(ERR_BAD_VALUE, uSize);
  240. return FALSE;
  241. }
  242. else if (uSize < sizeof(MIDIHDR31))
  243. {
  244. DebugErr(DBF_ERROR, "Invalid header size");
  245. LogParamError(ERR_BAD_VALUE, uSize);
  246. return FALSE;
  247. }
  248. if (pHeader->dwFlags & ~MHDR_VALID) {
  249. LogParamError(ERR_BAD_FLAGS, ((PMIDIHDR)pHeader)->dwFlags);
  250. return FALSE;
  251. }
  252. // Check buffer
  253. if (!(uType == TYPE_MIDIOUT
  254. ? ValidateReadPointer(pHeader->lpData, pHeader->dwBufferLength)
  255. : ValidateWritePointer(pHeader->lpData, pHeader->dwBufferLength))
  256. ) {
  257. DebugErr(DBF_ERROR, "Invalid buffer pointer");
  258. return FALSE;
  259. }
  260. }
  261. break;
  262. default:
  263. WinAssert(FALSE);
  264. break;
  265. }
  266. return TRUE;
  267. }
  268. #ifndef USE_KERNEL_VALIDATION
  269. /***************************************************************************
  270. * @doc INTERNAL
  271. *
  272. * @func BOOL | ValidateReadPointer | validates that a pointer is valid to
  273. * read from.
  274. *
  275. * @parm LPVOID | lpPoint| pointer to validate
  276. * @parm DWORD | dLen | supposed length of said pointer
  277. *
  278. * @rdesc Returns TRUE if <p> is a valid pointer
  279. * Returns FALSE if <p> is not a valid pointer
  280. *
  281. * @comm will generate error if the pointer is invalid
  282. *
  283. ***************************************************************************/
  284. BOOL ValidateReadPointer(PVOID pPoint, ULONG Len)
  285. {
  286. // For now just check access to first and last byte
  287. // Only validate if Len non zero. Midi APIs pass data in the
  288. // pointer and pass a length of zero. Otherwise on 64 bit machines
  289. // we look 4Gigs out from the pointer when we check Len-1 and Len==0.
  290. // That doesn't work very well.
  291. if (Len) {
  292. try {
  293. volatile BYTE b;
  294. b = ((PBYTE)pPoint)[0];
  295. b = ((PBYTE)pPoint)[Len - 1];
  296. } except(EXCEPTION_EXECUTE_HANDLER) {
  297. LogParamError(ERR_BAD_PTR, pPoint);
  298. return FALSE;
  299. }
  300. }
  301. return TRUE;
  302. }
  303. /***************************************************************************
  304. * @doc INTERNAL
  305. *
  306. * @func BOOL | ValidateWritePointer | validates that a pointer is valid to
  307. * write to.
  308. *
  309. * @parm LPVOID | lpPoint| pointer to validate
  310. * @parm DWORD | dLen | supposed length of said pointer
  311. *
  312. * @rdesc Returns TRUE if <p> is a valid pointer
  313. * Returns FALSE if <p> is not a valid pointer
  314. *
  315. * @comm will generate error if the pointer is invalid
  316. *
  317. ***************************************************************************/
  318. BOOL ValidateWritePointer(PVOID pPoint, ULONG Len)
  319. {
  320. // For now just check read and write access to first and last byte
  321. // Only validate if Len non zero. Midi APIs pass data in the
  322. // pointer and pass a length of zero. Otherwise on 64 bit machines
  323. // we look 4Gigs out from the pointer when we check Len-1 and Len==0.
  324. // That doesn't work very well.
  325. if (Len) {
  326. try {
  327. volatile BYTE b;
  328. b = ((PBYTE)pPoint)[0];
  329. ((PBYTE)pPoint)[0] = b;
  330. b = ((PBYTE)pPoint)[Len - 1];
  331. ((PBYTE)pPoint)[Len - 1] = b;
  332. } except(EXCEPTION_EXECUTE_HANDLER) {
  333. LogParamError(ERR_BAD_PTR, pPoint);
  334. return FALSE;
  335. }
  336. }
  337. return TRUE;
  338. }
  339. #endif // USE_KERNEL_VALIDATION
  340. /***************************************************************************
  341. * @doc INTERNAL
  342. *
  343. * @func BOOL | ValidDriverCallback |
  344. *
  345. * validates that a driver callback is valid, to be valid a driver
  346. * callback must be a valid window, task, or a function in a FIXED DLL
  347. * code segment.
  348. *
  349. * @parm DWORD | dwCallback | callback to validate
  350. * @parm DWORD | wFlags | driver callback flags
  351. *
  352. * @rdesc Returns 0 if <dwCallback> is a valid callback
  353. * Returns error condition if <dwCallback> is not a valid callback
  354. ***************************************************************************/
  355. BOOL ValidDriverCallback(HANDLE hCallback, DWORD dwFlags)
  356. {
  357. switch (dwFlags & DCB_TYPEMASK) {
  358. case DCB_WINDOW:
  359. if (!IsWindow(hCallback)) {
  360. LogParamError(ERR_BAD_HWND, hCallback);
  361. return FALSE;
  362. }
  363. break;
  364. case DCB_EVENT:
  365. //if (hCallback is not an event)
  366. // LogParamError(ERR_BAD_CALLBACK, hCallback);
  367. // return FALSE;
  368. //}
  369. break;
  370. case DCB_TASK:
  371. //if (IsBadCodePtr((FARPROC)hCallback)) {
  372. // LogParamError(ERR_BAD_CALLBACK, hCallback);
  373. // return FALSE;
  374. //}
  375. break;
  376. case DCB_FUNCTION:
  377. if (IsBadCodePtr((FARPROC)hCallback)) {
  378. LogParamError(ERR_BAD_CALLBACK, hCallback);
  379. return FALSE;
  380. }
  381. break;
  382. }
  383. return TRUE;
  384. }
  385. #ifndef USE_KERNEL_VALIDATION
  386. /**************************************************************************
  387. * @doc INTERNAL
  388. *
  389. * @func BOOL | ValidateString |
  390. *
  391. **************************************************************************/
  392. BOOL ValidateString(LPCSTR pPoint, DWORD Len)
  393. {
  394. // For now just check access - do a 'strnlen'
  395. try {
  396. volatile BYTE b;
  397. LPCSTR p = pPoint;
  398. while (Len--) {
  399. b = *p;
  400. if (!b) {
  401. break;
  402. }
  403. p++;
  404. }
  405. } except(EXCEPTION_EXECUTE_HANDLER) {
  406. LogParamError(ERR_BAD_STRING_PTR, pPoint);
  407. return FALSE;
  408. }
  409. return TRUE;
  410. }
  411. /**************************************************************************
  412. * @doc INTERNAL
  413. *
  414. * @func BOOL | ValidateStringW |
  415. *
  416. **************************************************************************/
  417. BOOL ValidateStringW(LPCWSTR pPoint, DWORD Len)
  418. {
  419. // For now just check access - do a 'strnlen'
  420. try {
  421. volatile WCHAR b;
  422. LPCWSTR p = pPoint;
  423. while (Len--) {
  424. b = *p;
  425. if (!b) {
  426. break;
  427. }
  428. p++;
  429. }
  430. } except(EXCEPTION_EXECUTE_HANDLER) {
  431. LogParamError(ERR_BAD_STRING_PTR, pPoint);
  432. return FALSE;
  433. }
  434. return TRUE;
  435. }
  436. #endif //USE_KERNEL_VALIDATION
  437. /**************************************************************************
  438. * @doc INTERNAL
  439. *
  440. * @func BOOL | ValidateHandle | validates a handle created with NewHandle
  441. *
  442. * @parm PHNDL | hLocal | handle returned from NewHandle
  443. * @parm UINT | wType | unique id describing handle type
  444. *
  445. * @rdesc Returns TRUE if <h> is a valid handle of type <wType>
  446. * Returns FALSE if <h> is not a valid handle
  447. *
  448. * @comm if the handle is invalid an error will be generated.
  449. *
  450. **************************************************************************/
  451. BOOL ValidateHandle(HANDLE hLocal, UINT uType)
  452. {
  453. BOOL OK;
  454. //
  455. // if the handle is less than 64k or a mapper id then
  456. // don't bother with the overhead of the try-except.
  457. //
  458. // BUGBUG: MM needs to be audited for WIN64!
  459. //
  460. // This code is a mess. The mapper ids are defined as 32-bit
  461. // unsigned values and then compared against a HANDLE? Of course
  462. // an unsigned 32-bit -1 will never equal a 64-bit -1 so on WIN64,
  463. // an exception is taken everytime an invalid handle is passed in.
  464. // Someone hacked in enough coercions to mask valid warnings for WIN64.
  465. // Even worse, there is at least one function that returns 0xffffffff
  466. // explicity versus a define or const value.
  467. //
  468. // For now, change the const compare to a handle compare and add an
  469. // invalid handle compare. This results in the same code on x86 as
  470. // before (the compiler folds all of the redundant compares), and doesn't
  471. // increase the codesize for IA64 (parallel compares are used) even though
  472. // the extra compare for WIN64 is useless.
  473. //
  474. if (hLocal < (HANDLE)0x10000 ||
  475. INVALID_HANDLE_VALUE == hLocal ||
  476. WAVE_MAPPER == (UINT_PTR)hLocal ||
  477. MIDI_MAPPER == (UINT_PTR)hLocal ||
  478. AUX_MAPPER == (UINT_PTR)hLocal)
  479. {
  480. LogParamError(ERR_BAD_HANDLE, hLocal);
  481. return FALSE;
  482. }
  483. try {
  484. OK = HtoPH(hLocal)->uType == uType;
  485. } except(EXCEPTION_EXECUTE_HANDLER) {
  486. LogParamError(ERR_BAD_HANDLE, hLocal);
  487. return FALSE;
  488. }
  489. return OK;
  490. }
  491. #if DBG
  492. char * Types[4] = {"Unknown callback type",
  493. "Window callback",
  494. "Task callback",
  495. "Function callback"};
  496. #endif
  497. /**************************************************************************
  498. * @doc INTERNAL
  499. *
  500. * @func BOOL | ValidateCallbackType | validates a callback address,
  501. * window handle, or task handle
  502. *
  503. * @parm PHNDL | hLocal | handle returned from NewHandle
  504. * @parm UINT | wType | unique id describing handle type
  505. *
  506. * @rdesc Returns TRUE if <h> is a valid handle of type <wType>
  507. * Returns FALSE if <h> is not a valid handle
  508. *
  509. * @comm if the handle is invalid an error will be generated.
  510. *
  511. **************************************************************************/
  512. BOOL ValidateCallbackType(DWORD_PTR dwCallback, UINT uType)
  513. {
  514. #define DCALLBACK_WINDOW HIWORD(CALLBACK_WINDOW) // dwCallback is a HWND
  515. #define DCALLBACK_TASK HIWORD(CALLBACK_TASK) // dwCallback is a HTASK
  516. #define DCALLBACK_FUNCTION HIWORD(CALLBACK_FUNCTION) // dwCallback is a FARPROC
  517. #define DCALLBACK_EVENT HIWORD(CALLBACK_EVENT) // dwCallback is an EVENT
  518. UINT type = uType & HIWORD(CALLBACK_TYPEMASK);
  519. #if DBG
  520. if (type>5) {
  521. type = 0;
  522. }
  523. dprintf3(("Validating Callback, type=%d (%hs), handle=%8x", type, Types[type], dwCallback));
  524. #endif
  525. switch (type) {
  526. case DCALLBACK_WINDOW:
  527. return(IsWindow((HWND)dwCallback));
  528. break;
  529. case DCALLBACK_EVENT:
  530. {
  531. // ?? how to verify that this is an event handle??
  532. //DWORD dwFlags;
  533. //GetHandleInformation((HANDLE)dwCallback, &dwFlags);
  534. return TRUE;
  535. }
  536. break;
  537. case DCALLBACK_FUNCTION:
  538. return(!(IsBadCodePtr((FARPROC)dwCallback)));
  539. break;
  540. case DCALLBACK_TASK:
  541. if (THREAD_PRIORITY_ERROR_RETURN == GetThreadPriority((HANDLE)dwCallback)) {
  542. dprintf1(("Invalid callback task handle"));
  543. // I suspect we do not have the correct thread handle, in
  544. // which case we can only return TRUE.
  545. //return(FALSE);
  546. }
  547. return(TRUE);
  548. break;
  549. }
  550. return TRUE;
  551. }
  552. /**************************************************************************
  553. @doc INTERNAL
  554. @func void | dout | Output debug string if debug flag is set
  555. @parm LPSTR | szString
  556. **************************************************************************/
  557. #if DBG
  558. int fDebug = 1;
  559. #else
  560. int fDebug = 0;
  561. #endif
  562. //void dout(LPSTR szString)
  563. //{
  564. // if (fDebug) {
  565. // OutputDebugStringA(szString);
  566. // }
  567. //}
  568. #ifdef LATER
  569. This routine should probably be replaced in the headers by redefining
  570. to use OutputDebugString
  571. #endif
  572. #undef OutputDebugStr
  573. // Make our function visible
  574. /*****************************************************************************
  575. * @doc EXTERNAL DDK
  576. *
  577. * @api void | OutputDebugStr | This function sends a debugging message
  578. * directly to the COM1 port or to a secondary monochrome display
  579. * adapter. Because it bypasses DOS, it can be called by low-level
  580. * callback functions and other code at interrupt time.
  581. *
  582. * @parm LPSTR | lpOutputString | Specifies a far pointer to a
  583. * null-terminated string.
  584. *
  585. * @comm This function is available only in the debugging version of
  586. * Windows. The DebugOutput keyname in the [mmsystem]
  587. * section of SYSTEM.INI controls where the debugging information is
  588. * sent. If fDebugOutput is 0, all debug output is disabled.
  589. ******************************************************************************/
  590. /*****************************************************************************
  591. * This function is basicly the same as OutputDebugString() in KERNEL.
  592. *
  593. *
  594. * DESCRIPTION: outputs a string to the debugger
  595. *
  596. * ENTRY: szString - string to output
  597. *
  598. * EXIT:
  599. * none
  600. * USES:
  601. * flags
  602. *
  603. *****************************************************************************/
  604. VOID APIENTRY OutputDebugStr(LPCSTR szString)
  605. {
  606. OutputDebugStringA((LPSTR)szString); // Will always be an ASCII string
  607. // When the MM WOW thunk is changed to call OutputDebugString directly
  608. // we can remove this routine from our code
  609. }
  610. #endif // DEBUG_RETAIL
  611. #if DBG
  612. int winmmDebugLevel = 0;
  613. /***************************************************************************
  614. @doc INTERNAL
  615. @api void | winmmDbgOut | This function sends output to the current
  616. debug output device.
  617. @parm LPSTR | lpszFormat | Pointer to a printf style format string.
  618. @parm ??? | ... | Args.
  619. @rdesc There is no return value.
  620. ****************************************************************************/
  621. extern BOOL Quiet = FALSE;
  622. void winmmDbgOut(LPSTR lpszFormat, ...)
  623. {
  624. char buf[512];
  625. UINT n;
  626. va_list va;
  627. if (Quiet) {
  628. return;
  629. }
  630. n = wsprintf(buf, "WINMM(p%d:t%d): ", GetCurrentProcessId(), GetCurrentThreadId());
  631. va_start(va, lpszFormat);
  632. n += vsprintf(buf+n, lpszFormat, va);
  633. va_end(va);
  634. buf[n++] = '\n';
  635. buf[n] = 0;
  636. OutputDebugString(buf);
  637. Sleep(0); // let terminal catch up
  638. }
  639. /***************************************************************************
  640. @doc INTERNAL
  641. @api void | dDbgAssert | This function prints an assertion message.
  642. @parm LPSTR | exp | Pointer to the expression string.
  643. @parm LPSTR | file | Pointer to the file name.
  644. @parm int | line | The line number.
  645. @rdesc There is no return value.
  646. ****************************************************************************/
  647. void dDbgAssert(LPSTR exp, LPSTR file, int line)
  648. {
  649. dprintf(("Assertion failure:"));
  650. dprintf((" Exp: %s", exp));
  651. dprintf((" File: %s, line: %d", file, line));
  652. DebugBreak();
  653. }
  654. #else // Still need to export this thing to help others
  655. void winmmDbgOut(LPSTR lpszFormat, ...)
  656. {
  657. }
  658. #endif // DBG