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.

703 lines
19 KiB

  1. //---------------------------------------------------------------------------
  2. // log.cpp - theme logging routines (shared in "inc" directory)
  3. //---------------------------------------------------------------------------
  4. #include "stdafx.h"
  5. //---------------------------------------------------------------------------
  6. #include "log.h"
  7. #include <time.h>
  8. #include <psapi.h>
  9. #include "cfile.h"
  10. #include "tmreg.h"
  11. //---------------------------------------------------------------------------
  12. //---- undo the defines that turn these into _xxx ----
  13. #undef Log
  14. #undef LogStartUp
  15. #undef LogShutDown
  16. #undef LogControl
  17. #undef TimeToStr
  18. #undef StartTimer
  19. #undef StopTimer
  20. #undef OpenLogFile
  21. #undef CloseLogFile
  22. #undef LogOptionOn
  23. #undef GetMemUsage
  24. #undef GetUserCount
  25. #undef GetGdiCount
  26. //---------------------------------------------------------------------------
  27. #define DEFAULT_LOGNAME L"c:\\Themes.log"
  28. //---------------------------------------------------------------------------
  29. struct LOGNAMEINFO
  30. {
  31. LPCSTR pszOption;
  32. LPCSTR pszDescription;
  33. };
  34. //---------------------------------------------------------------------------
  35. #define MAKE_LOG_STRINGS
  36. #include "logopts.h" // log options as strings
  37. //-----------------------------------------------------------------
  38. static const WCHAR szDayOfWeekArray[7][4] = { L"Sun", L"Mon", L"Tue", L"Wed",
  39. L"Thu", L"Fri", L"Sat" } ;
  40. static const WCHAR szMonthOfYearArray[12][4] = { L"Jan", L"Feb", L"Mar",
  41. L"Apr", L"May", L"Jun", L"Jul", L"Aug", L"Sep", L"Oct", L"Nov", L"Dec" } ;
  42. //-----------------------------------------------------------------
  43. #define OPTIONCNT (ARRAYSIZE(LogNames))
  44. #define LOGPROMPT "enter log cmd here."
  45. //-----------------------------------------------------------------
  46. //---- unprotected vars (set on init) ----
  47. static WCHAR _szUtilProcessName[MAX_PATH] = {0};
  48. static WCHAR _szLogAppName[MAX_PATH] = {0};
  49. static CRITICAL_SECTION csLogFile = {0};
  50. static BOOL bcsLogInited = FALSE;
  51. static WCHAR szLogFileName[MAX_PATH+1];
  52. static BOOL fLogInitialized = FALSE;
  53. //---- protected vars (thread safe) ----
  54. static UCHAR uLogOptions[OPTIONCNT]; // protected by csLogFile
  55. static UCHAR uBreakOptions[OPTIONCNT]; // protected by csLogFile
  56. static DWORD dwLogStartTimer = 0; // protected by csLogFile
  57. static char szLastOptions[999] = {0}; // protected by csLogFile
  58. static char szLogCmd[999] = {0}; // protected by csLogFile
  59. static int iIndentCount = 0; // protected by csLogFile
  60. static WCHAR s_szWorkerBuffer[512]; // protected by csLogFile
  61. static WCHAR s_szLogBuffer[512]; // protected by csLogFile
  62. static CHAR s_szConBuffer[512]; // protected by csLogFile
  63. static CSimpleFile *pLogFile = NULL; // protected by csLogFile
  64. //-----------------------------------------------------------------
  65. void ParseLogOptions(UCHAR *uOptions, LPCSTR pszName, LPCSTR pszOptions, BOOL fEcho);
  66. //-----------------------------------------------------------------
  67. void RawCon(LPCSTR pszFormat, ...)
  68. {
  69. CAutoCS cs(&csLogFile);
  70. va_list args;
  71. va_start(args, pszFormat);
  72. //---- format caller's string ----
  73. wvsprintfA(s_szConBuffer, pszFormat, args);
  74. OutputDebugStringA(s_szConBuffer);
  75. va_end(args);
  76. }
  77. //-----------------------------------------------------------------
  78. void SetDefaultLoggingOptions()
  79. {
  80. RESOURCE HKEY hklm = NULL;
  81. HRESULT hr = S_OK;
  82. //---- open hklm ----
  83. int code32 = RegOpenKeyEx(HKEY_LOCAL_MACHINE, THEMEMGR_REGKEY, 0,
  84. KEY_READ, &hklm);
  85. if (code32 != ERROR_SUCCESS)
  86. {
  87. hr = MakeErrorLast();
  88. goto exit;
  89. }
  90. //---- read the "LogCmd" value ----
  91. WCHAR szValBuff[MAX_PATH];
  92. hr = RegistryStrRead(hklm, THEMEPROP_LOGCMD, szValBuff, ARRAYSIZE(szValBuff));
  93. if (SUCCEEDED(hr))
  94. {
  95. USES_CONVERSION;
  96. ParseLogOptions(uLogOptions, "Log", W2A(szValBuff), FALSE);
  97. }
  98. //---- read the "BreakCmd" value ----
  99. hr = RegistryStrRead(hklm, THEMEPROP_BREAKCMD, szValBuff, ARRAYSIZE(szValBuff));
  100. if (SUCCEEDED(hr))
  101. {
  102. USES_CONVERSION;
  103. ParseLogOptions(uBreakOptions, "Break", W2A(szValBuff), FALSE);
  104. }
  105. //---- read the "LogAppName" value ----
  106. RegistryStrRead(hklm, THEMEPROP_LOGAPPNAME, _szLogAppName, ARRAYSIZE(_szLogAppName));
  107. exit:
  108. if (hklm)
  109. RegCloseKey(hklm);
  110. }
  111. //-----------------------------------------------------------------
  112. BOOL LogStartUp()
  113. {
  114. BOOL fInit = FALSE;
  115. dwLogStartTimer = StartTimer();
  116. pLogFile = new CSimpleFile;
  117. if (! pLogFile)
  118. goto exit;
  119. //---- reset all log options ----
  120. for (int i=0; i < OPTIONCNT; i++)
  121. {
  122. uLogOptions[i] = 0;
  123. uBreakOptions[i] = 0;
  124. }
  125. //---- turn on default OUTPUT options ----
  126. uLogOptions[LO_CONSOLE] = TRUE;
  127. uLogOptions[LO_APPID] = TRUE;
  128. uLogOptions[LO_THREADID] = TRUE;
  129. //---- turn on default FILTER options ----
  130. uLogOptions[LO_ERROR] = TRUE;
  131. uLogOptions[LO_ASSERT] = TRUE;
  132. uLogOptions[LO_BREAK] = TRUE;
  133. uLogOptions[LO_PARAMS] = TRUE;
  134. uLogOptions[LO_ALWAYS] = TRUE;
  135. //---- turn on default BREAK options ----
  136. uBreakOptions[LO_ERROR] = TRUE;
  137. uBreakOptions[LO_ASSERT] = TRUE;
  138. InitializeCriticalSection(&csLogFile);
  139. bcsLogInited = TRUE;
  140. //---- get process name (log has its own copy) ----
  141. WCHAR szPath[MAX_PATH];
  142. if (! GetModuleFileNameW( NULL, szPath, ARRAYSIZE(szPath) ))
  143. goto exit;
  144. WCHAR szDrive[_MAX_DRIVE], szDir[_MAX_DIR], szExt[_MAX_EXT];
  145. _wsplitpath(szPath, szDrive, szDir, _szUtilProcessName, szExt);
  146. strcpy(szLogCmd, LOGPROMPT);
  147. lstrcpy(szLogFileName, DEFAULT_LOGNAME);
  148. fLogInitialized = TRUE;
  149. SetDefaultLoggingOptions();
  150. fInit = TRUE;
  151. exit:
  152. return fInit;
  153. }
  154. //---------------------------------------------------------------------------
  155. BOOL LogShutDown()
  156. {
  157. fLogInitialized = FALSE;
  158. SAFE_DELETE(pLogFile);
  159. if (bcsLogInited)
  160. {
  161. DeleteCriticalSection(&csLogFile);
  162. }
  163. return TRUE;
  164. }
  165. //---------------------------------------------------------------------------
  166. void GetDateString(WCHAR *pszDateBuff, ULONG uMaxBuffChars)
  167. {
  168. // Sent Date
  169. SYSTEMTIME stNow;
  170. WCHAR szMonth[10], szWeekDay[12] ;
  171. GetLocalTime(&stNow);
  172. lstrcpynW(szWeekDay, szDayOfWeekArray[stNow.wDayOfWeek], ARRAYSIZE(szWeekDay)) ;
  173. lstrcpynW(szMonth, szMonthOfYearArray[stNow.wMonth-1], ARRAYSIZE(szMonth)) ;
  174. wsprintfW(pszDateBuff, L"%s, %u %s %u %2d:%02d:%02d ", szWeekDay, stNow.wDay,
  175. szMonth, stNow.wYear, stNow.wHour,
  176. stNow.wMinute, stNow.wSecond) ;
  177. }
  178. //---------------------------------------------------------------------------
  179. void LogMsgToFile(LPCWSTR pszMsg)
  180. {
  181. CAutoCS autoCritSect(&csLogFile);
  182. HRESULT hr;
  183. BOOL fWasOpen = pLogFile->IsOpen();
  184. if (! fWasOpen)
  185. {
  186. BOOL fNewFile = !FileExists(szLogFileName);
  187. hr = pLogFile->Append(szLogFileName, TRUE);
  188. if (FAILED(hr))
  189. goto exit;
  190. //---- write hdr if new file ----
  191. if (fNewFile)
  192. {
  193. WCHAR pszBuff[100];
  194. GetDateString(pszBuff, ARRAYSIZE(pszBuff));
  195. pLogFile->Printf(L"Theme log - %s\r\n\r\n", pszBuff);
  196. }
  197. }
  198. pLogFile->Write((void*)pszMsg, lstrlen(pszMsg)*sizeof(WCHAR));
  199. exit:
  200. if (! fWasOpen)
  201. pLogFile->Close();
  202. }
  203. //---------------------------------------------------------------------------
  204. void SimpleFileName(LPCSTR pszNarrowFile, OUT LPWSTR pszSimpleBuff)
  205. {
  206. USES_CONVERSION;
  207. WCHAR *pszFile = A2W(pszNarrowFile);
  208. //---- remove current dir marker for VS error navigation ----
  209. if ((pszFile[0] == L'.') && (pszFile[1] == L'\\'))
  210. {
  211. wsprintf(pszSimpleBuff, L"f:\\nt\\shell\\Themes\\UxTheme\\%s", pszFile+2);
  212. if (! FileExists(pszSimpleBuff))
  213. wsprintf(pszSimpleBuff, L"f:\\nt\\shell\\Themes\\ThemeSel\\%s", pszFile+2);
  214. if (! FileExists(pszSimpleBuff))
  215. wsprintf(pszSimpleBuff, L"f:\\nt\\shell\\Themes\\packthem\\%s", pszFile+2);
  216. if (! FileExists(pszSimpleBuff))
  217. wsprintf(pszSimpleBuff, L"%s", pszFile+2);
  218. }
  219. else
  220. lstrcpy(pszSimpleBuff, pszFile);
  221. }
  222. //-----------------------------------------------------------------
  223. #ifdef DEBUG // pulls in psapi.dll
  224. int GetMemUsage()
  225. {
  226. ULONG ulReturnLength;
  227. VM_COUNTERS vmCounters;
  228. if (!NT_SUCCESS(NtQueryInformationProcess(GetCurrentProcess(),
  229. ProcessVmCounters,
  230. &vmCounters,
  231. sizeof(vmCounters),
  232. &ulReturnLength)))
  233. {
  234. ZeroMemory(&vmCounters, sizeof(vmCounters));
  235. }
  236. return static_cast<int>(vmCounters.WorkingSetSize);
  237. }
  238. #else
  239. int GetMemUsage()
  240. {
  241. return 0;
  242. }
  243. #endif
  244. //-----------------------------------------------------------------
  245. int GetUserCount()
  246. {
  247. HANDLE hp = GetCurrentProcess();
  248. return GetGuiResources(hp, GR_USEROBJECTS);
  249. }
  250. //-----------------------------------------------------------------
  251. int GetGdiCount()
  252. {
  253. HANDLE hp = GetCurrentProcess();
  254. return GetGuiResources(hp, GR_GDIOBJECTS);
  255. }
  256. //-----------------------------------------------------------------
  257. void LogWorker(UCHAR uLogOption, LPCSTR pszSrcFile, int iLineNum, int iEntryExitCode, LPCWSTR pszMsg)
  258. {
  259. CAutoCS cs(&csLogFile);
  260. WCHAR *p = s_szWorkerBuffer;
  261. BOOL fBreaking = (uBreakOptions[uLogOption]);
  262. if (fBreaking)
  263. {
  264. OutputDebugString(L"\r\n"); // blank line at beginning
  265. WCHAR fn[_MAX_PATH+1];
  266. SimpleFileName(pszSrcFile, fn);
  267. wsprintf(s_szWorkerBuffer, L"%s [%d]: BREAK at %s(%d):\r\n",
  268. _szUtilProcessName, GetCurrentThreadId(), fn, iLineNum);
  269. OutputDebugString(s_szWorkerBuffer);
  270. }
  271. //---- PRE API entry/exit indent adjustment ----
  272. if (iEntryExitCode == -1)
  273. {
  274. if (iIndentCount >= 2)
  275. iIndentCount -= 2;
  276. }
  277. //---- apply indenting ----
  278. for (int i=0; i < iIndentCount; i++)
  279. *p++ = ' ';
  280. //---- POST API entry/exit indent adjustment ----
  281. if (iEntryExitCode == 1)
  282. {
  283. iIndentCount += 2;
  284. }
  285. //---- apply app id ----
  286. if (uLogOptions[LO_APPID])
  287. {
  288. wsprintf(p, L"%s ", _szUtilProcessName);
  289. p += lstrlen(p);
  290. }
  291. //---- apply thread id ----
  292. if (uLogOptions[LO_THREADID])
  293. {
  294. wsprintf(p, L"[%d] ", GetCurrentThreadId());
  295. p += lstrlen(p);
  296. }
  297. //---- apply src id ----
  298. if (uLogOptions[LO_SRCID])
  299. {
  300. WCHAR fn[_MAX_PATH+1];
  301. SimpleFileName(pszSrcFile, fn);
  302. if (fBreaking)
  303. wsprintf(p, L"BREAK at %s(%d) : ", fn, iLineNum);
  304. else
  305. wsprintf(p, L"%s(%d) : ", fn, iLineNum);
  306. p += lstrlen(p);
  307. }
  308. //---- apply timer id ----
  309. if (uLogOptions[LO_TIMERID])
  310. {
  311. DWORD dwTicks = StopTimer(dwLogStartTimer);
  312. WCHAR buff[100];
  313. TimeToStr(dwTicks, buff);
  314. wsprintf(p, L"Timer: %s ", buff);
  315. p += lstrlen(p);
  316. }
  317. //---- apply clock id ----
  318. if (uLogOptions[LO_CLOCKID])
  319. {
  320. SYSTEMTIME stNow;
  321. GetLocalTime(&stNow);
  322. wsprintf(p, L"Clock: %02d:%02d:%02d.%03d ", stNow.wHour,
  323. stNow.wMinute, stNow.wSecond, stNow.wMilliseconds);
  324. p += lstrlen(p);
  325. }
  326. //---- apply USER count ----
  327. if (uLogOptions[LO_USERCOUNT])
  328. {
  329. wsprintf(p, L"UserCount=%d ", GetUserCount());
  330. p += lstrlen(p);
  331. }
  332. //---- apply GDI count ----
  333. if (uLogOptions[LO_GDICOUNT])
  334. {
  335. wsprintf(p, L"GDICount=%d ", GetGdiCount());
  336. p += lstrlen(p);
  337. }
  338. //---- apply MEM usage ----
  339. if (uLogOptions[LO_MEMUSAGE])
  340. {
  341. int iUsage = GetMemUsage();
  342. wsprintf(p, L"MemUsage=%d ", iUsage);
  343. p += lstrlen(p);
  344. }
  345. //---- add "Assert:" or "Error:" strings as needed ----
  346. if (uLogOption == LO_ASSERT)
  347. {
  348. lstrcpy(p, L"Assert: ");
  349. p += lstrlen(p);
  350. }
  351. else if (uLogOption == LO_ERROR)
  352. {
  353. lstrcpy(p, L"Error: ");
  354. p += lstrlen(p);
  355. }
  356. //---- apply caller's msg ----
  357. lstrcpy(p, pszMsg);
  358. p += lstrlen(p);
  359. *p++ = '\r'; // end with CR
  360. *p++ = '\n'; // end with newline
  361. *p = 0; // terminate string
  362. //---- log to CONSOLE and/or FILE ----
  363. if (uLogOptions[LO_CONSOLE])
  364. OutputDebugString(s_szWorkerBuffer);
  365. if (uLogOptions[LO_LOGFILE])
  366. LogMsgToFile(s_szWorkerBuffer);
  367. //---- process Heap Check ----
  368. if (uLogOptions[LO_HEAPCHECK])
  369. {
  370. HANDLE hh = GetProcessHeap();
  371. HeapValidate(hh, 0, NULL);
  372. }
  373. if (fBreaking)
  374. OutputDebugString(L"\r\n"); // blank line at end
  375. }
  376. //-----------------------------------------------------------------
  377. HRESULT OpenLogFile(LPCWSTR pszLogFileName)
  378. {
  379. CAutoCS cs(&csLogFile);
  380. HRESULT hr = pLogFile->Create(pszLogFileName, TRUE);
  381. return hr;
  382. }
  383. //-----------------------------------------------------------------
  384. void CloseLogFile()
  385. {
  386. CAutoCS cs(&csLogFile);
  387. if (pLogFile)
  388. pLogFile->Close();
  389. }
  390. //-----------------------------------------------------------------
  391. void Log(UCHAR uLogOption, LPCSTR pszSrcFile, int iLineNum, int iEntryExitCode, LPCWSTR pszFormat, ...)
  392. {
  393. if (fLogInitialized)
  394. {
  395. CAutoCS cs(&csLogFile);
  396. //---- only log for a specified process? ----
  397. if (* _szLogAppName)
  398. {
  399. if (lstrcmpi(_szLogAppName, _szUtilProcessName) != 0)
  400. return;
  401. }
  402. while (1)
  403. {
  404. if (strncmp(szLogCmd, LOGPROMPT, 6)!=0)
  405. {
  406. LogControl(szLogCmd, TRUE);
  407. strcpy(szLogCmd, LOGPROMPT);
  408. DEBUG_BREAK;
  409. }
  410. else
  411. break;
  412. }
  413. if ((uLogOption >= 0) || (uLogOption < OPTIONCNT))
  414. {
  415. if (uLogOptions[uLogOption])
  416. {
  417. //---- apply caller's msg ----
  418. va_list args;
  419. va_start(args, pszFormat);
  420. wvsprintfW(s_szLogBuffer, pszFormat, args);
  421. va_end(args);
  422. LogWorker(uLogOption, pszSrcFile, iLineNum, iEntryExitCode, s_szLogBuffer);
  423. }
  424. if ((uBreakOptions[uLogOption]) && (uLogOption != LO_ASSERT))
  425. DEBUG_BREAK;
  426. }
  427. }
  428. }
  429. //-----------------------------------------------------------------
  430. int MatchLogOption(LPCSTR lszOption)
  431. {
  432. for (int i=0; i < OPTIONCNT; i++)
  433. {
  434. if (lstrcmpiA(lszOption, LogNames[i].pszOption)==0)
  435. return i;
  436. }
  437. return -1; // not found
  438. }
  439. //-----------------------------------------------------------------
  440. void ParseLogOptions(UCHAR *uOptions, LPCSTR pszName, LPCSTR pszOptions, BOOL fEcho)
  441. {
  442. CAutoCS cs(&csLogFile);
  443. if (! fLogInitialized)
  444. return;
  445. //---- ignore debugger's multiple eval of same expression ----
  446. if (strcmp(pszOptions, szLastOptions)==0)
  447. return;
  448. //---- make a copy of options so we can put NULLs in it ----
  449. BOOL fErrors = FALSE;
  450. char szOptions[999];
  451. char *pszErrorText = NULL;
  452. strcpy(szOptions, pszOptions);
  453. //---- process each option in szOption ----
  454. char *p = strchr(szOptions, '.');
  455. if (p) // easy termination for NTSD users
  456. *p = 0;
  457. p = szOptions;
  458. while (*p == ' ')
  459. p++;
  460. while ((p) && (*p))
  461. {
  462. //---- find end of current option "p" ----
  463. char *q = strchr(p, ' ');
  464. if (q)
  465. *q = 0;
  466. UCHAR fSet = TRUE;
  467. if (*p == '+')
  468. p++;
  469. else if (*p == '-')
  470. {
  471. fSet = FALSE;
  472. p++;
  473. }
  474. if (! fErrors) // point to first error in case one is found
  475. pszErrorText = p;
  476. int iLogOpt = MatchLogOption(p);
  477. if (iLogOpt != -1)
  478. {
  479. if (iLogOpt == LO_ALL)
  480. {
  481. for (int i=0; i < OPTIONCNT; i++)
  482. uOptions[i] = fSet;
  483. }
  484. else
  485. uOptions[iLogOpt] = fSet;
  486. }
  487. else
  488. fErrors = TRUE;
  489. //---- skip to next valid space ----
  490. if (! q)
  491. break;
  492. q++;
  493. while (*q == ' ')
  494. q++;
  495. p = q;
  496. }
  497. //---- fixup inconsistent option settings ----
  498. if (! uOptions[LO_LOGFILE])
  499. uOptions[LO_CONSOLE] = TRUE;
  500. if ((! fErrors) && (fEcho))
  501. {
  502. //---- display active log options ----
  503. BOOL fNoneSet = TRUE;
  504. RawCon("Active %s Options:\n", pszName);
  505. for (int i=0; i < LO_ALL; i++)
  506. {
  507. if (uOptions[i])
  508. {
  509. RawCon("+%s ", LogNames[i].pszOption);
  510. fNoneSet = FALSE;
  511. }
  512. }
  513. if (fNoneSet)
  514. RawCon("<none>\n");
  515. else
  516. RawCon("\n");
  517. //---- display active log filters (except "all") ----
  518. RawCon("Active Msg Filters:\n ");
  519. for (i=LO_ALL+1; i < OPTIONCNT; i++)
  520. {
  521. if (uOptions[i])
  522. {
  523. RawCon("+%s ", LogNames[i].pszOption);
  524. fNoneSet = FALSE;
  525. }
  526. }
  527. if (fNoneSet)
  528. RawCon("<none>\n");
  529. else
  530. RawCon("\n");
  531. }
  532. else if (fErrors)
  533. {
  534. //---- one or more bad options - display available options ----
  535. RawCon("Error - unrecognized %s option: %s\n", pszName, pszErrorText);
  536. if (fEcho)
  537. {
  538. RawCon("Available Log Options:\n");
  539. for (int i=0; i < LO_ALL; i++)
  540. {
  541. RawCon(" +%-12s %s\n",
  542. LogNames[i].pszOption, LogNames[i].pszDescription);
  543. }
  544. RawCon("Available Msg Filters:\n");
  545. for (i=LO_ALL; i < OPTIONCNT; i++)
  546. {
  547. RawCon(" +%-12s %s\n",
  548. LogNames[i].pszOption, LogNames[i].pszDescription);
  549. }
  550. }
  551. }
  552. strcpy(szLastOptions, pszOptions);
  553. }
  554. //-----------------------------------------------------------------
  555. void LogControl(LPCSTR pszOptions, BOOL fEcho)
  556. {
  557. ParseLogOptions(uLogOptions, "Log", pszOptions, fEcho);
  558. }
  559. //---------------------------------------------------------------------------
  560. DWORD StartTimer()
  561. {
  562. LARGE_INTEGER now;
  563. QueryPerformanceCounter(&now);
  564. return DWORD(now.QuadPart);
  565. }
  566. //---------------------------------------------------------------------------
  567. DWORD StopTimer(DWORD dwStartTime)
  568. {
  569. LARGE_INTEGER now;
  570. QueryPerformanceCounter(&now);
  571. return DWORD(now.QuadPart) - dwStartTime;
  572. }
  573. //---------------------------------------------------------------------------
  574. void TimeToStr(UINT uRaw, WCHAR *pszBuff)
  575. {
  576. LARGE_INTEGER freq;
  577. QueryPerformanceFrequency(&freq);
  578. DWORD Freq = (UINT)freq.QuadPart;
  579. const _int64 factor6 = 1000000;
  580. _int64 secs = uRaw*factor6/Freq;
  581. wsprintfW(pszBuff, L"%u.%04u secs", UINT(secs/factor6), UINT(secs%factor6)/100);
  582. }
  583. //---------------------------------------------------------------------------
  584. BOOL LogOptionOn(int iLogOption)
  585. {
  586. if ((iLogOption < 0) || (iLogOption >= OPTIONCNT))
  587. return FALSE;
  588. return (uLogOptions[iLogOption] != 0);
  589. }
  590. //---------------------------------------------------------------------------