Team Fortress 2 Source Code as on 22/4/2020
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.

627 lines
16 KiB

  1. //===== Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //===========================================================================//
  8. #include "tier0/platform.h"
  9. #if defined( PLATFORM_WINDOWS_PC )
  10. #define WIN_32_LEAN_AND_MEAN
  11. #include <windows.h> // Currently needed for IsBadReadPtr and IsBadWritePtr
  12. #pragma comment(lib,"user32.lib") // For MessageBox
  13. #endif
  14. #include "tier0/minidump.h"
  15. #include "tier0/stacktools.h"
  16. #include <assert.h>
  17. #include <stdio.h>
  18. #include <string.h>
  19. #include <stdarg.h>
  20. #include <stdlib.h>
  21. #include "color.h"
  22. #include "tier0/dbg.h"
  23. #include "tier0/threadtools.h"
  24. #include "tier0/icommandline.h"
  25. #include <math.h>
  26. #if defined( _X360 )
  27. #include "xbox/xbox_console.h"
  28. #endif
  29. #include "tier0/etwprof.h"
  30. #ifndef STEAM
  31. #define PvRealloc realloc
  32. #define PvAlloc malloc
  33. #endif
  34. // memdbgon must be the last include file in a .cpp file!!!
  35. #include "tier0/memdbgon.h"
  36. #if defined( ENABLE_RUNTIME_STACK_TRANSLATION )
  37. #pragma optimize( "g", off ) //variable argument functions seem to screw up stack walking unless this optimization is disabled
  38. #pragma warning( disable: 4748 ) // Turn off the warning telling us that optimizations are off if /GS is on
  39. #endif
  40. DEFINE_LOGGING_CHANNEL_NO_TAGS( LOG_LOADING, "LOADING" );
  41. //-----------------------------------------------------------------------------
  42. // Stack attachment management
  43. //-----------------------------------------------------------------------------
  44. #if defined( ENABLE_RUNTIME_STACK_TRANSLATION )
  45. static bool s_bCallStacksWithAllWarnings = false; //if true, attach a call stack to every SPEW_WARNING message. Warning()/DevWarning()/...
  46. static int s_iWarningMaxCallStackLength = 5;
  47. #define AutomaticWarningCallStackLength() (s_bCallStacksWithAllWarnings ? s_iWarningMaxCallStackLength : 0)
  48. void _Warning_AlwaysSpewCallStack_Enable( bool bEnable )
  49. {
  50. s_bCallStacksWithAllWarnings = bEnable;
  51. }
  52. void _Warning_AlwaysSpewCallStack_Length( int iMaxCallStackLength )
  53. {
  54. s_iWarningMaxCallStackLength = iMaxCallStackLength;
  55. }
  56. static bool s_bCallStacksWithAllErrors = false; //if true, attach a call stack to every SPEW_ERROR message. Mostly just Error()
  57. static int s_iErrorMaxCallStackLength = 20; //default to higher output with an error since we're quitting anyways
  58. #define AutomaticErrorCallStackLength() (s_bCallStacksWithAllErrors ? s_iErrorMaxCallStackLength : 0)
  59. void _Error_AlwaysSpewCallStack_Enable( bool bEnable )
  60. {
  61. s_bCallStacksWithAllErrors = bEnable;
  62. }
  63. void _Error_AlwaysSpewCallStack_Length( int iMaxCallStackLength )
  64. {
  65. s_iErrorMaxCallStackLength = iMaxCallStackLength;
  66. }
  67. #else //#if defined( ENABLE_RUNTIME_STACK_TRANSLATION )
  68. #define AutomaticWarningCallStackLength() 0
  69. #define AutomaticErrorCallStackLength() 0
  70. void _Warning_AlwaysSpewCallStack_Enable( bool bEnable )
  71. {
  72. }
  73. void _Warning_AlwaysSpewCallStack_Length( int iMaxCallStackLength )
  74. {
  75. }
  76. void _Error_AlwaysSpewCallStack_Enable( bool bEnable )
  77. {
  78. }
  79. void _Error_AlwaysSpewCallStack_Length( int iMaxCallStackLength )
  80. {
  81. }
  82. #endif //#if defined( ENABLE_RUNTIME_STACK_TRANSLATION )
  83. void _ExitOnFatalAssert( const tchar* pFile, int line )
  84. {
  85. Log_Msg( LOG_ASSERT, _T("Fatal assert failed: %s, line %d. Application exiting.\n"), pFile, line );
  86. // only write out minidumps if we're not in the debugger
  87. if ( !Plat_IsInDebugSession() )
  88. {
  89. WriteMiniDump();
  90. }
  91. Log_Msg( LOG_DEVELOPER, _T("_ExitOnFatalAssert\n") );
  92. Plat_ExitProcess( EXIT_FAILURE );
  93. }
  94. //-----------------------------------------------------------------------------
  95. // Templates to assist in validating pointers:
  96. //-----------------------------------------------------------------------------
  97. PLATFORM_INTERFACE void _AssertValidReadPtr( void* ptr, int count/* = 1*/ )
  98. {
  99. #if defined( _WIN32 ) && !defined( _X360 )
  100. Assert( !IsBadReadPtr( ptr, count ) );
  101. #else
  102. Assert( !count || ptr );
  103. #endif
  104. }
  105. PLATFORM_INTERFACE void _AssertValidWritePtr( void* ptr, int count/* = 1*/ )
  106. {
  107. #if defined( _WIN32 ) && !defined( _X360 )
  108. Assert( !IsBadWritePtr( ptr, count ) );
  109. #else
  110. Assert( !count || ptr );
  111. #endif
  112. }
  113. PLATFORM_INTERFACE void _AssertValidReadWritePtr( void* ptr, int count/* = 1*/ )
  114. {
  115. #if defined( _WIN32 ) && !defined( _X360 )
  116. Assert(!( IsBadWritePtr(ptr, count) || IsBadReadPtr(ptr,count)));
  117. #else
  118. Assert( !count || ptr );
  119. #endif
  120. }
  121. PLATFORM_INTERFACE void _AssertValidStringPtr( const tchar* ptr, int maxchar/* = 0xFFFFFF */ )
  122. {
  123. #if defined( _WIN32 ) && !defined( _X360 )
  124. #ifdef TCHAR_IS_CHAR
  125. Assert( !IsBadStringPtr( ptr, maxchar ) );
  126. #else
  127. Assert( !IsBadStringPtrW( ptr, maxchar ) );
  128. #endif
  129. #else
  130. Assert( ptr );
  131. #endif
  132. }
  133. void AppendCallStackToLogMessage( tchar *formattedMessage, int iMessageLength, int iAppendCallStackLength )
  134. {
  135. #if defined( ENABLE_RUNTIME_STACK_TRANSLATION )
  136. # if defined( TCHAR_IS_CHAR ) //I'm horrible with unicode and I don't plan on testing this with wide characters just yet
  137. if( iAppendCallStackLength > 0 )
  138. {
  139. int iExistingMessageLength = (int)strlen( formattedMessage ); //no V_strlen in tier 0, plus we're only compiling this for windows and 360. Seems safe
  140. formattedMessage += iExistingMessageLength;
  141. iMessageLength -= iExistingMessageLength;
  142. if( iMessageLength <= 32 )
  143. return; //no room for anything useful
  144. //append directly to the spew message
  145. if( (iExistingMessageLength > 0) && (formattedMessage[-1] == '\n') )
  146. {
  147. --formattedMessage;
  148. ++iMessageLength;
  149. }
  150. //append preface
  151. int iAppendedLength = _snprintf( formattedMessage, iMessageLength, _T("\nCall Stack:\n\t") );
  152. void **CallStackBuffer = (void **)stackalloc( iAppendCallStackLength * sizeof( void * ) );
  153. int iCount = GetCallStack( CallStackBuffer, iAppendCallStackLength, 2 );
  154. if( TranslateStackInfo( CallStackBuffer, iCount, formattedMessage + iAppendedLength, iMessageLength - iAppendedLength, _T("\n\t") ) == 0 )
  155. {
  156. //failure
  157. formattedMessage[0] = '\0'; //this is pointing at where we wrote "\nCall Stack:\n\t"
  158. }
  159. else
  160. {
  161. iAppendedLength += (int)strlen( formattedMessage + iAppendedLength ); //no V_strlen in tier 0, plus we're only compiling this for windows and 360. Seems safe
  162. if( iAppendedLength < iMessageLength )
  163. {
  164. formattedMessage[iAppendedLength] = '\n'; //Add another newline.
  165. ++iAppendedLength;
  166. formattedMessage[iAppendedLength] = '\0';
  167. }
  168. }
  169. }
  170. # else
  171. AssertMsg( false, "Fixme" );
  172. # endif
  173. #endif
  174. }
  175. // Forward declare for internal use only.
  176. CLoggingSystem *GetGlobalLoggingSystem();
  177. #define Log_LegacyHelperColor_Stack( Channel, Severity, Color, MessageFormat, AppendCallStackLength ) \
  178. do \
  179. { \
  180. CLoggingSystem *pLoggingSystem = GetGlobalLoggingSystem(); \
  181. if ( pLoggingSystem->IsChannelEnabled( Channel, Severity ) ) \
  182. { \
  183. tchar formattedMessage[MAX_LOGGING_MESSAGE_LENGTH]; \
  184. va_list args; \
  185. va_start( args, MessageFormat ); \
  186. Tier0Internal_vsntprintf( formattedMessage, MAX_LOGGING_MESSAGE_LENGTH, MessageFormat, args ); \
  187. va_end( args ); \
  188. AppendCallStackToLogMessage( formattedMessage, MAX_LOGGING_MESSAGE_LENGTH, AppendCallStackLength ); \
  189. pLoggingSystem->LogDirect( Channel, Severity, Color, formattedMessage ); \
  190. } \
  191. } while( 0 )
  192. #define Log_LegacyHelperColor( Channel, Severity, Color, MessageFormat ) Log_LegacyHelperColor_Stack( Channel, Severity, Color, MessageFormat, 0 )
  193. #define Log_LegacyHelper_Stack( Channel, Severity, MessageFormat, AppendCallStackLength ) Log_LegacyHelperColor_Stack( Channel, Severity, pLoggingSystem->GetChannelColor( Channel ), MessageFormat, AppendCallStackLength )
  194. #define Log_LegacyHelper( Channel, Severity, MessageFormat ) Log_LegacyHelperColor( Channel, Severity, pLoggingSystem->GetChannelColor( Channel ), MessageFormat )
  195. void Msg( const tchar* pMsgFormat, ... )
  196. {
  197. Log_LegacyHelper( LOG_GENERAL, LS_MESSAGE, pMsgFormat );
  198. }
  199. void Warning( const tchar *pMsgFormat, ... )
  200. {
  201. Log_LegacyHelper_Stack( LOG_GENERAL, LS_WARNING, pMsgFormat, AutomaticWarningCallStackLength() );
  202. }
  203. void Warning_SpewCallStack( int iMaxCallStackLength, const tchar *pMsgFormat, ... )
  204. {
  205. Log_LegacyHelper_Stack( LOG_GENERAL, LS_WARNING, pMsgFormat, iMaxCallStackLength );
  206. }
  207. void Error( const tchar *pMsgFormat, ... )
  208. {
  209. Log_LegacyHelper_Stack( LOG_GENERAL, LS_ERROR, pMsgFormat, AutomaticErrorCallStackLength() );
  210. }
  211. void Error_SpewCallStack( int iMaxCallStackLength, const tchar *pMsgFormat, ... )
  212. {
  213. Log_LegacyHelper_Stack( LOG_GENERAL, LS_ERROR, pMsgFormat, iMaxCallStackLength );
  214. }
  215. //-----------------------------------------------------------------------------
  216. // A couple of super-common dynamic spew messages, here for convenience
  217. // These looked at the "developer" group, print if it's level 1 or higher
  218. //-----------------------------------------------------------------------------
  219. void DevMsg( int level, const tchar* pMsgFormat, ... )
  220. {
  221. LoggingChannelID_t channel = level >= 2 ? LOG_DEVELOPER_VERBOSE : LOG_DEVELOPER;
  222. Log_LegacyHelper( channel, LS_MESSAGE, pMsgFormat );
  223. }
  224. void DevWarning( int level, const tchar *pMsgFormat, ... )
  225. {
  226. LoggingChannelID_t channel = level >= 2 ? LOG_DEVELOPER_VERBOSE : LOG_DEVELOPER;
  227. Log_LegacyHelper( channel, LS_WARNING, pMsgFormat );
  228. }
  229. void DevMsg( const tchar *pMsgFormat, ... )
  230. {
  231. Log_LegacyHelper( LOG_DEVELOPER, LS_MESSAGE, pMsgFormat );
  232. }
  233. void DevWarning( const tchar *pMsgFormat, ... )
  234. {
  235. Log_LegacyHelper( LOG_DEVELOPER, LS_WARNING, pMsgFormat );
  236. }
  237. void ConColorMsg( const Color& clr, const tchar* pMsgFormat, ... )
  238. {
  239. Log_LegacyHelperColor( LOG_CONSOLE, LS_MESSAGE, clr, pMsgFormat );
  240. }
  241. void ConMsg( const tchar *pMsgFormat, ... )
  242. {
  243. Log_LegacyHelper( LOG_CONSOLE, LS_MESSAGE, pMsgFormat );
  244. }
  245. void ConDMsg( const tchar *pMsgFormat, ... )
  246. {
  247. Log_LegacyHelper( LOG_DEVELOPER_CONSOLE, LS_MESSAGE, pMsgFormat );
  248. }
  249. // If we don't have a function from math.h, then it doesn't link certain floating-point
  250. // functions in and printfs with %f cause runtime errors in the C libraries.
  251. PLATFORM_INTERFACE float CrackSmokingCompiler( float a )
  252. {
  253. return (float)fabs( a );
  254. }
  255. void* Plat_SimpleLog( const tchar* file, int line )
  256. {
  257. FILE* f = _tfopen( _T("simple.log"), _T("at+") );
  258. _ftprintf( f, _T("%s:%i\n"), file, line );
  259. fclose( f );
  260. return NULL;
  261. }
  262. //-----------------------------------------------------------------------------
  263. // Purpose: For debugging startup times, etc.
  264. // Input : *fmt -
  265. // ... -
  266. //-----------------------------------------------------------------------------
  267. void COM_TimestampedLog( char const *fmt, ... )
  268. {
  269. static float s_LastStamp = 0.0;
  270. static bool s_bShouldLog = false;
  271. static bool s_bShouldLogToConsole = false;
  272. static bool s_bShouldLogToETW = false;
  273. static bool s_bChecked = false;
  274. static bool s_bFirstWrite = false;
  275. if ( !s_bChecked )
  276. {
  277. s_bShouldLog = ( CommandLine()->CheckParm( "-profile" ) ) ? true : false;
  278. s_bShouldLogToConsole = ( CommandLine()->ParmValue( "-profile", 0.0f ) != 0.0f ) ? true : false;
  279. s_bShouldLogToETW = ( CommandLine()->CheckParm( "-etwprofile" ) ) ? true : false;
  280. if ( s_bShouldLogToETW )
  281. {
  282. s_bShouldLog = true;
  283. }
  284. s_bChecked = true;
  285. }
  286. if ( !s_bShouldLog )
  287. {
  288. return;
  289. }
  290. char string[1024];
  291. va_list argptr;
  292. va_start( argptr, fmt );
  293. Tier0Internal_vsnprintf( string, sizeof( string ), fmt, argptr );
  294. va_end( argptr );
  295. float curStamp = Plat_FloatTime();
  296. #if defined( _X360 )
  297. XBX_rTimeStampLog( curStamp, string );
  298. #elif defined( _PS3 )
  299. Log_Warning( LOG_LOADING, "%8.4f / %8.4f: %s\n", curStamp, curStamp - s_LastStamp, string );
  300. #endif
  301. if ( IsPC() )
  302. {
  303. // If ETW profiling is enabled then do it only.
  304. if ( s_bShouldLogToETW )
  305. {
  306. ETWMark( string );
  307. }
  308. else
  309. {
  310. if ( !s_bFirstWrite )
  311. {
  312. unlink( "timestamped.log" );
  313. s_bFirstWrite = true;
  314. }
  315. FILE* fp = fopen( "timestamped.log", "at+" );
  316. fprintf( fp, "%8.4f / %8.4f: %s\n", curStamp, curStamp - s_LastStamp, string );
  317. fclose( fp );
  318. }
  319. if ( s_bShouldLogToConsole )
  320. {
  321. Msg( "%8.4f / %8.4f: %s\n", curStamp, curStamp - s_LastStamp, string );
  322. }
  323. }
  324. s_LastStamp = curStamp;
  325. }
  326. #ifdef IS_WINDOWS_PC
  327. class CHardwareBreakPoint
  328. {
  329. public:
  330. enum EOpCode
  331. {
  332. BRK_SET = 0,
  333. BRK_UNSET,
  334. };
  335. CHardwareBreakPoint()
  336. {
  337. m_eOperation = BRK_SET;
  338. m_pvAddress = 0;
  339. m_hThread = 0;
  340. m_hThreadEvent = 0;
  341. m_nRegister = 0;
  342. m_bSuccess = false;
  343. }
  344. const void *m_pvAddress;
  345. HANDLE m_hThread;
  346. EHardwareBreakpointType m_eType;
  347. EHardwareBreakpointSize m_eSize;
  348. HANDLE m_hThreadEvent;
  349. int m_nRegister;
  350. EOpCode m_eOperation;
  351. bool m_bSuccess;
  352. static void SetBits( DWORD_PTR& dw, int lowBit, int bits, int newValue );
  353. static DWORD WINAPI ThreadProc( LPVOID lpParameter );
  354. };
  355. void CHardwareBreakPoint::SetBits( DWORD_PTR& dw, int lowBit, int bits, int newValue )
  356. {
  357. DWORD_PTR mask = (1 << bits) - 1;
  358. dw = (dw & ~(mask << lowBit)) | (newValue << lowBit);
  359. }
  360. DWORD WINAPI CHardwareBreakPoint::ThreadProc( LPVOID lpParameter )
  361. {
  362. CHardwareBreakPoint *h = reinterpret_cast< CHardwareBreakPoint * >( lpParameter );
  363. SuspendThread( h->m_hThread );
  364. // Get current context
  365. CONTEXT ct = {0};
  366. ct.ContextFlags = CONTEXT_DEBUG_REGISTERS;
  367. GetThreadContext(h->m_hThread,&ct);
  368. int FlagBit = 0;
  369. bool Dr0Busy = false;
  370. bool Dr1Busy = false;
  371. bool Dr2Busy = false;
  372. bool Dr3Busy = false;
  373. if (ct.Dr7 & 1)
  374. Dr0Busy = true;
  375. if (ct.Dr7 & 4)
  376. Dr1Busy = true;
  377. if (ct.Dr7 & 16)
  378. Dr2Busy = true;
  379. if (ct.Dr7 & 64)
  380. Dr3Busy = true;
  381. if ( h->m_eOperation == CHardwareBreakPoint::BRK_UNSET )
  382. {
  383. // Remove
  384. if (h->m_nRegister == 0)
  385. {
  386. FlagBit = 0;
  387. ct.Dr0 = 0;
  388. Dr0Busy = false;
  389. }
  390. if (h->m_nRegister == 1)
  391. {
  392. FlagBit = 2;
  393. ct.Dr1 = 0;
  394. Dr1Busy = false;
  395. }
  396. if (h->m_nRegister == 2)
  397. {
  398. FlagBit = 4;
  399. ct.Dr2 = 0;
  400. Dr2Busy = false;
  401. }
  402. if (h->m_nRegister == 3)
  403. {
  404. FlagBit = 6;
  405. ct.Dr3 = 0;
  406. Dr3Busy = false;
  407. }
  408. ct.Dr7 &= ~(1 << FlagBit);
  409. }
  410. else
  411. {
  412. if (!Dr0Busy)
  413. {
  414. h->m_nRegister = 0;
  415. ct.Dr0 = (DWORD_PTR)h->m_pvAddress;
  416. Dr0Busy = true;
  417. }
  418. else if (!Dr1Busy)
  419. {
  420. h->m_nRegister = 1;
  421. ct.Dr1 = (DWORD_PTR)h->m_pvAddress;
  422. Dr1Busy = true;
  423. }
  424. else if (!Dr2Busy)
  425. {
  426. h->m_nRegister = 2;
  427. ct.Dr2 = (DWORD_PTR)h->m_pvAddress;
  428. Dr2Busy = true;
  429. }
  430. else if (!Dr3Busy)
  431. {
  432. h->m_nRegister = 3;
  433. ct.Dr3 = (DWORD_PTR)h->m_pvAddress;
  434. Dr3Busy = true;
  435. }
  436. else
  437. {
  438. h->m_bSuccess = false;
  439. ResumeThread(h->m_hThread);
  440. SetEvent(h->m_hThreadEvent);
  441. return 0;
  442. }
  443. ct.Dr6 = 0;
  444. int st = 0;
  445. if (h->m_eType == BREAKPOINT_EXECUTE)
  446. st = 0;
  447. if (h->m_eType == BREAKPOINT_READWRITE)
  448. st = 3;
  449. if (h->m_eType == BREAKPOINT_WRITE)
  450. st = 1;
  451. int le = 0;
  452. if (h->m_eSize == BREAKPOINT_SIZE_1)
  453. le = 0;
  454. if (h->m_eSize == BREAKPOINT_SIZE_2)
  455. le = 1;
  456. if (h->m_eSize == BREAKPOINT_SIZE_4)
  457. le = 3;
  458. if (h->m_eSize == BREAKPOINT_SIZE_8)
  459. le = 2;
  460. SetBits( ct.Dr7, 16 + h->m_nRegister*4, 2, st );
  461. SetBits( ct.Dr7, 18 + h->m_nRegister*4, 2, le );
  462. SetBits( ct.Dr7, h->m_nRegister*2,1,1);
  463. }
  464. ct.ContextFlags = CONTEXT_DEBUG_REGISTERS;
  465. SetThreadContext(h->m_hThread,&ct);
  466. ResumeThread( h->m_hThread );
  467. h->m_bSuccess = true;
  468. SetEvent( h->m_hThreadEvent );
  469. return 0;
  470. }
  471. HardwareBreakpointHandle_t SetHardwareBreakpoint( EHardwareBreakpointType eType, EHardwareBreakpointSize eSize, const void *pvLocation )
  472. {
  473. CHardwareBreakPoint *h = new CHardwareBreakPoint();
  474. h->m_pvAddress = pvLocation;
  475. h->m_eSize = eSize;
  476. h->m_eType = eType;
  477. HANDLE hThread = GetCurrentThread();
  478. h->m_hThread = hThread;
  479. if ( hThread == GetCurrentThread() )
  480. {
  481. DWORD nThreadId = GetCurrentThreadId();
  482. h->m_hThread = OpenThread( THREAD_ALL_ACCESS, 0, nThreadId );
  483. }
  484. h->m_hThreadEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
  485. h->m_eOperation = CHardwareBreakPoint::BRK_SET; // Set Break
  486. CreateThread( 0, 0, CHardwareBreakPoint::ThreadProc, (LPVOID)h, 0, 0 );
  487. WaitForSingleObject( h->m_hThreadEvent,INFINITE );
  488. CloseHandle( h->m_hThreadEvent );
  489. h->m_hThreadEvent = 0;
  490. if ( hThread == GetCurrentThread() )
  491. {
  492. CloseHandle( h->m_hThread );
  493. }
  494. h->m_hThread = hThread;
  495. if ( !h->m_bSuccess )
  496. {
  497. delete h;
  498. return (HardwareBreakpointHandle_t)0;
  499. }
  500. return (HardwareBreakpointHandle_t)h;
  501. }
  502. bool ClearHardwareBreakpoint( HardwareBreakpointHandle_t handle )
  503. {
  504. CHardwareBreakPoint *h = reinterpret_cast< CHardwareBreakPoint* >( handle );
  505. if ( !h )
  506. {
  507. return false;
  508. }
  509. bool bOpened = false;
  510. if ( h->m_hThread == GetCurrentThread() )
  511. {
  512. DWORD nThreadId = GetCurrentThreadId();
  513. h->m_hThread = OpenThread( THREAD_ALL_ACCESS, 0, nThreadId );
  514. bOpened = true;
  515. }
  516. h->m_hThreadEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
  517. h->m_eOperation = CHardwareBreakPoint::BRK_UNSET; // Remove Break
  518. CreateThread( 0,0,CHardwareBreakPoint::ThreadProc, (LPVOID)h, 0,0 );
  519. WaitForSingleObject( h->m_hThreadEvent, INFINITE );
  520. CloseHandle( h->m_hThreadEvent );
  521. h->m_hThreadEvent = 0;
  522. if ( bOpened )
  523. {
  524. CloseHandle( h->m_hThread );
  525. }
  526. delete h;
  527. return true;
  528. }
  529. #endif // IS_WINDOWS_PC