mirror of https://github.com/tongzx/nt5src
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.
636 lines
23 KiB
636 lines
23 KiB
/*******************************************************************************
|
|
* SPDebug.h *
|
|
*-----------*
|
|
* Description:
|
|
* This header file contains debug output services for SAPI5
|
|
*-------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
*******************************************************************************/
|
|
|
|
#pragma once
|
|
|
|
#include <TCHAR.h>
|
|
#include <crtdbg.h>
|
|
|
|
#ifdef ASSERT_WITH_STACK
|
|
#include "AssertWithStack.h"
|
|
#endif
|
|
|
|
const TCHAR g_szSpDebugKey[] = _T("SPDebug");
|
|
const TCHAR g_szSpDebugFuncTraceReportMode[] = _T("FuncTraceMode");
|
|
const TCHAR g_szSpDebugFuncTraceReportFile[] = _T("FuncTraceFile");
|
|
const TCHAR g_szSpDebugParamInfoReportMode[] = _T("ParamInfoMode");
|
|
const TCHAR g_szSpDebugParamInfoReportFile[] = _T("ParamInfoFile");
|
|
const TCHAR g_szSpDebugDumpInfoReportMode[] = _T("DumpInfoMode");
|
|
const TCHAR g_szSpDebugDumpInfoReportFile[] = _T("DumpInfoFile");
|
|
const TCHAR g_szSpDebugAssertReportMode[] = _T("AssertMode");
|
|
const TCHAR g_szSpDebugAssertReportFile[] = _T("AssertFile");
|
|
const TCHAR g_szSpDebugHRFailReportMode[] = _T("HRFailMode");
|
|
const TCHAR g_szSpDebugHRFailReportFile[] = _T("HRFailFile");
|
|
|
|
const TCHAR g_szSpDebugAssertSettingsReReadEachTime[] = _T("AssertSettingsReReadEachTime");
|
|
const TCHAR g_szSpDebugServerOnStart[] = _T("DebugServerOnStart");
|
|
const TCHAR g_szSpDebugClientOnStart[] = _T("DebugClientOnStart");
|
|
|
|
const TCHAR g_szSpDebugLog[] = _T("c:\\spdebug.log");
|
|
|
|
#ifdef _DEBUG
|
|
|
|
class CSpDebug
|
|
{
|
|
public:
|
|
|
|
CSpDebug()
|
|
{
|
|
m_mutex = NULL;
|
|
m_reportModePrev = -1;
|
|
m_hfilePrev = NULL;
|
|
Read();
|
|
}
|
|
|
|
~CSpDebug()
|
|
{
|
|
if (m_mutex != NULL)
|
|
{
|
|
CloseHandle(m_mutex);
|
|
}
|
|
}
|
|
|
|
BOOL FuncTrace(BOOL fEnter = TRUE)
|
|
{
|
|
return fEnter
|
|
? Enter(_CRT_WARN, m_FuncTraceMode, m_szFuncTraceFile)
|
|
: Leave();
|
|
}
|
|
|
|
BOOL ParamInfo(BOOL fEnter = TRUE)
|
|
{
|
|
return fEnter
|
|
? Enter(_CRT_WARN, m_ParamInfoMode, m_szParamInfoFile)
|
|
: Leave();
|
|
}
|
|
|
|
BOOL DumpInfo(BOOL fEnter = TRUE)
|
|
{
|
|
return fEnter
|
|
? Enter(_CRT_WARN, m_DumpInfoMode, m_szDumpInfoFile)
|
|
: Leave();
|
|
}
|
|
|
|
BOOL Assert(BOOL fEnter = TRUE)
|
|
{
|
|
if (m_fAssertSettingsReReadEachTime)
|
|
Read();
|
|
|
|
return fEnter
|
|
? Enter(_CRT_ASSERT, m_AssertMode, m_szAssertFile)
|
|
: Leave();
|
|
}
|
|
|
|
BOOL HRFail(BOOL fEnter = TRUE)
|
|
{
|
|
return fEnter
|
|
? Enter(_CRT_WARN, m_HRFailMode, m_szHRFailFile)
|
|
: Leave();
|
|
}
|
|
|
|
BOOL DebugServerOnStart()
|
|
{
|
|
return m_fDebugServerOnStart;
|
|
}
|
|
|
|
BOOL DebugClientOnStart()
|
|
{
|
|
return m_fDebugClientOnStart;
|
|
}
|
|
|
|
private:
|
|
|
|
void Read()
|
|
{
|
|
HKEY hkeyDebug;
|
|
RegCreateKeyEx(
|
|
HKEY_CLASSES_ROOT,
|
|
g_szSpDebugKey,
|
|
0,
|
|
NULL,
|
|
0,
|
|
KEY_READ | KEY_WRITE,
|
|
NULL,
|
|
&hkeyDebug,
|
|
NULL);
|
|
if (hkeyDebug == NULL)
|
|
{
|
|
RegCreateKeyEx(
|
|
HKEY_CLASSES_ROOT,
|
|
g_szSpDebugKey,
|
|
0,
|
|
NULL,
|
|
0,
|
|
KEY_READ,
|
|
NULL,
|
|
&hkeyDebug,
|
|
NULL);
|
|
}
|
|
|
|
DWORD dw = sizeof(m_fAssertSettingsReReadEachTime);
|
|
if (RegQueryValueEx(
|
|
hkeyDebug,
|
|
g_szSpDebugAssertSettingsReReadEachTime,
|
|
NULL,
|
|
NULL,
|
|
LPBYTE(&m_fAssertSettingsReReadEachTime),
|
|
&dw) != ERROR_SUCCESS)
|
|
{
|
|
m_fAssertSettingsReReadEachTime = FALSE;
|
|
RegSetValueEx(
|
|
hkeyDebug,
|
|
g_szSpDebugAssertSettingsReReadEachTime,
|
|
NULL,
|
|
REG_DWORD,
|
|
LPBYTE(&m_fAssertSettingsReReadEachTime),
|
|
sizeof(m_fAssertSettingsReReadEachTime));
|
|
}
|
|
|
|
ReadFor(
|
|
hkeyDebug,
|
|
g_szSpDebugFuncTraceReportMode,
|
|
g_szSpDebugFuncTraceReportFile,
|
|
&m_FuncTraceMode,
|
|
m_szFuncTraceFile,
|
|
0,
|
|
g_szSpDebugLog);
|
|
ReadFor(
|
|
hkeyDebug,
|
|
g_szSpDebugParamInfoReportMode,
|
|
g_szSpDebugParamInfoReportFile,
|
|
&m_ParamInfoMode,
|
|
m_szParamInfoFile,
|
|
0,
|
|
g_szSpDebugLog);
|
|
ReadFor(
|
|
hkeyDebug,
|
|
g_szSpDebugDumpInfoReportMode,
|
|
g_szSpDebugDumpInfoReportFile,
|
|
&m_DumpInfoMode,
|
|
m_szDumpInfoFile,
|
|
_CRTDBG_MODE_DEBUG,
|
|
g_szSpDebugLog);
|
|
ReadFor(
|
|
hkeyDebug,
|
|
g_szSpDebugAssertReportMode,
|
|
g_szSpDebugAssertReportFile,
|
|
&m_AssertMode,
|
|
m_szAssertFile,
|
|
_CRTDBG_MODE_WNDW,
|
|
g_szSpDebugLog);
|
|
ReadFor(
|
|
hkeyDebug,
|
|
g_szSpDebugHRFailReportMode,
|
|
g_szSpDebugHRFailReportFile,
|
|
&m_HRFailMode,
|
|
m_szHRFailFile,
|
|
_CRTDBG_MODE_DEBUG,
|
|
g_szSpDebugLog);
|
|
|
|
dw = sizeof(m_fDebugServerOnStart);
|
|
if (RegQueryValueEx(
|
|
hkeyDebug,
|
|
g_szSpDebugServerOnStart,
|
|
NULL,
|
|
NULL,
|
|
LPBYTE(&m_fDebugServerOnStart),
|
|
&dw) != ERROR_SUCCESS)
|
|
{
|
|
m_fDebugServerOnStart = FALSE;
|
|
RegSetValueEx(
|
|
hkeyDebug,
|
|
g_szSpDebugServerOnStart,
|
|
NULL,
|
|
REG_DWORD,
|
|
LPBYTE(&m_fDebugServerOnStart),
|
|
sizeof(m_fDebugServerOnStart));
|
|
}
|
|
|
|
dw = sizeof(m_fDebugClientOnStart);
|
|
if (RegQueryValueEx(
|
|
hkeyDebug,
|
|
g_szSpDebugClientOnStart,
|
|
NULL,
|
|
NULL,
|
|
LPBYTE(&m_fDebugClientOnStart),
|
|
&dw) != ERROR_SUCCESS)
|
|
{
|
|
m_fDebugClientOnStart = FALSE;
|
|
RegSetValueEx(
|
|
hkeyDebug,
|
|
g_szSpDebugClientOnStart,
|
|
NULL,
|
|
REG_DWORD,
|
|
LPBYTE(&m_fDebugClientOnStart),
|
|
sizeof(m_fDebugClientOnStart));
|
|
}
|
|
|
|
RegCloseKey(hkeyDebug);
|
|
}
|
|
|
|
void ReadFor(
|
|
HKEY hkey,
|
|
const TCHAR * pszModeValueName,
|
|
const TCHAR * pszFileValueName,
|
|
DWORD * pdwModeValue,
|
|
TCHAR * pszFileValue,
|
|
DWORD dwDefaultModeValue,
|
|
const TCHAR * pszDefaultFileValue)
|
|
{
|
|
DWORD dw = sizeof(*pdwModeValue);
|
|
if (RegQueryValueEx(
|
|
hkey,
|
|
pszModeValueName,
|
|
NULL,
|
|
NULL,
|
|
LPBYTE(pdwModeValue),
|
|
&dw) != ERROR_SUCCESS)
|
|
{
|
|
*pdwModeValue = dwDefaultModeValue;
|
|
RegSetValueEx(
|
|
hkey,
|
|
pszModeValueName,
|
|
NULL,
|
|
REG_DWORD,
|
|
LPBYTE(pdwModeValue),
|
|
sizeof(*pdwModeValue));
|
|
}
|
|
|
|
dw = MAX_PATH;
|
|
if (RegQueryValueEx(
|
|
hkey,
|
|
pszFileValueName,
|
|
NULL,
|
|
NULL,
|
|
LPBYTE(pszFileValue),
|
|
&dw) != ERROR_SUCCESS)
|
|
{
|
|
_tcscpy(pszFileValue, pszDefaultFileValue);
|
|
RegSetValueEx(
|
|
hkey,
|
|
pszFileValueName,
|
|
NULL,
|
|
REG_SZ,
|
|
LPBYTE(pszFileValue),
|
|
MAX_PATH);
|
|
}
|
|
}
|
|
|
|
BOOL Enter(int reportType, DWORD &reportMode, TCHAR * pszFile)
|
|
{
|
|
if (reportMode != 0)
|
|
{
|
|
// We'll hold the mutex, until the caller also calls Leave
|
|
if (m_mutex == NULL)
|
|
{
|
|
m_mutex = CreateMutex(NULL, FALSE, _T("SpDebug"));
|
|
}
|
|
WaitForSingleObject(m_mutex, INFINITE);
|
|
|
|
m_reportType = reportType;
|
|
m_reportModePrev = _CrtSetReportMode(reportType, reportMode);
|
|
if (reportMode & _CRTDBG_MODE_FILE)
|
|
{
|
|
HANDLE hfile = CreateFile(
|
|
pszFile,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
OPEN_ALWAYS,
|
|
0,
|
|
NULL);
|
|
SetFilePointer(hfile, 0, NULL, FILE_END);
|
|
m_hfilePrev = (_HFILE)_CrtSetReportFile(reportType, (_HFILE)hfile);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL Leave()
|
|
{
|
|
int reportMode = _CrtSetReportMode(m_reportType, m_reportModePrev);
|
|
if (reportMode & _CRTDBG_MODE_FILE)
|
|
{
|
|
CloseHandle((_HFILE)_CrtSetReportFile(m_reportType, (_HFILE)m_hfilePrev));
|
|
}
|
|
|
|
ReleaseMutex(m_mutex);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
private:
|
|
|
|
HANDLE m_mutex;
|
|
|
|
int m_reportType;
|
|
int m_reportModePrev;
|
|
_HFILE m_hfilePrev;
|
|
|
|
BOOL m_fAssertSettingsReReadEachTime;
|
|
|
|
DWORD m_FuncTraceMode;
|
|
TCHAR m_szFuncTraceFile[MAX_PATH + 1];
|
|
DWORD m_ParamInfoMode;
|
|
TCHAR m_szParamInfoFile[MAX_PATH + 1];
|
|
DWORD m_DumpInfoMode;
|
|
TCHAR m_szDumpInfoFile[MAX_PATH + 1];
|
|
DWORD m_AssertMode;
|
|
TCHAR m_szAssertFile[MAX_PATH + 1];
|
|
DWORD m_HRFailMode;
|
|
TCHAR m_szHRFailFile[MAX_PATH + 1];
|
|
|
|
BOOL m_fDebugServerOnStart;
|
|
BOOL m_fDebugClientOnStart;
|
|
};
|
|
|
|
inline CSpDebug *PSpDebug()
|
|
{
|
|
static CSpDebug debug;
|
|
return &debug;
|
|
}
|
|
|
|
class CSpFuncTrace
|
|
{
|
|
public:
|
|
|
|
CSpFuncTrace(PCHAR pFuncName)
|
|
{
|
|
m_pFuncName = pFuncName;
|
|
if (PSpDebug()->FuncTrace())
|
|
{
|
|
_RPT1( _CRT_WARN, "\nEntering Function: %s\n", m_pFuncName );
|
|
PSpDebug()->FuncTrace(FALSE);
|
|
}
|
|
}
|
|
|
|
~CSpFuncTrace()
|
|
{
|
|
if (PSpDebug()->FuncTrace())
|
|
{
|
|
_RPT1( _CRT_WARN, "Leaving Function: %s\n", m_pFuncName );
|
|
PSpDebug()->FuncTrace(FALSE);
|
|
}
|
|
}
|
|
|
|
private:
|
|
|
|
PCHAR m_pFuncName;
|
|
};
|
|
|
|
#endif // _DEBUG
|
|
|
|
//=== User macros ==============================================================
|
|
|
|
#ifdef _DEBUG
|
|
|
|
#define SPDBG_FUNC(name) \
|
|
CSpFuncTrace functrace(name)
|
|
|
|
#if defined(ASSERT_WITH_STACK) && !defined(_WIN64)
|
|
#define SPDBG_REPORT_ON_FAIL(hr) \
|
|
do \
|
|
{ \
|
|
HRESULT _hr = (hr); \
|
|
if (FAILED(_hr) && PSpDebug()->HRFail()) \
|
|
{ \
|
|
SYSTEMTIME sysTime; \
|
|
GetLocalTime(&sysTime); \
|
|
CHAR pszHrWithTime[100]; \
|
|
sprintf(pszHrWithTime, "%lX\n\n%d.%d.%d %02d:%02d:%02d", \
|
|
_hr, \
|
|
sysTime.wMonth,sysTime.wDay,sysTime.wYear, \
|
|
sysTime.wHour,sysTime.wMinute,sysTime.wSecond); \
|
|
PCHAR pszStack = \
|
|
(PCHAR)_alloca( \
|
|
cchMaxAssertStackLevelStringLen * \
|
|
cfrMaxAssertStackLevels + 1); \
|
|
GetStringFromStackLevels(0, 10, pszStack); \
|
|
_RPT4(_CRT_WARN, \
|
|
"%s(%d): Failed HR = %s\n\n%s\n", \
|
|
__FILE__, \
|
|
__LINE__, \
|
|
pszHrWithTime, \
|
|
pszStack); \
|
|
PSpDebug()->HRFail(FALSE); \
|
|
} \
|
|
} while (0)
|
|
#else // ASSERT_WITH_STACK & !_WIN64
|
|
#define SPDBG_REPORT_ON_FAIL(hr) \
|
|
do \
|
|
{ \
|
|
HRESULT _hr = (hr); \
|
|
if (FAILED(_hr) && PSpDebug()->HRFail()) \
|
|
{ \
|
|
_RPT3(_CRT_WARN, "%s(%d): Failed HR = %lX\n", __FILE__, __LINE__, (_hr) );\
|
|
PSpDebug()->HRFail(FALSE); \
|
|
} \
|
|
} while (0)
|
|
#endif // ASSERT_WITH_STACK
|
|
|
|
#define SPDBG_ASSERT(expr) \
|
|
do \
|
|
{ \
|
|
if (!(expr)) \
|
|
{ \
|
|
if (PSpDebug()->Assert()) \
|
|
{ \
|
|
_ASSERTE( expr ); \
|
|
PSpDebug()->Assert(FALSE); \
|
|
} \
|
|
} \
|
|
} \
|
|
while (0)
|
|
|
|
#define SPDBG_VERIFY(expr) \
|
|
SPDBG_ASSERT(expr)
|
|
|
|
#define SPDBG_PMSG0(format) \
|
|
do \
|
|
{ \
|
|
if (PSpDebug()->ParamInfo()) \
|
|
{ \
|
|
_RPT0(_CRT_WARN, format); \
|
|
PSpDebug()->ParamInfo(FALSE); \
|
|
} \
|
|
} while (0)
|
|
#define SPDBG_PMSG1(format, arg1) \
|
|
do \
|
|
{ \
|
|
if (PSpDebug()->ParamInfo()) \
|
|
{ \
|
|
_RPT1(_CRT_WARN, format, arg1); \
|
|
PSpDebug()->ParamInfo(FALSE); \
|
|
} \
|
|
} while (0)
|
|
#define SPDBG_PMSG2(format, arg1, arg2) \
|
|
do \
|
|
{ \
|
|
if (PSpDebug()->ParamInfo()) \
|
|
{ \
|
|
_RPT2(_CRT_WARN, format, arg1, arg2); \
|
|
PSpDebug()->ParamInfo(FALSE); \
|
|
} \
|
|
} while (0)
|
|
#define SPDBG_PMSG3(format, arg1, arg2, arg3) \
|
|
do \
|
|
{ \
|
|
if (PSpDebug()->ParamInfo()) \
|
|
{ \
|
|
_RPT3(_CRT_WARN, format, arg1, arg2, arg3); \
|
|
PSpDebug()->ParamInfo(FALSE); \
|
|
} \
|
|
} while (0)
|
|
#define SPDBG_PMSG4(format, arg1, arg2, arg3, arg4) \
|
|
do \
|
|
{ \
|
|
if (PSpDebug()->ParamInfo()) \
|
|
{ \
|
|
_RPT4(_CRT_WARN, format, arg1, arg2, arg3, arg4); \
|
|
PSpDebug()->ParamInfo(FALSE); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define SPDBG_DMSG0(format) \
|
|
do \
|
|
{ \
|
|
if (PSpDebug()->DumpInfo()) \
|
|
{ \
|
|
_RPT0(_CRT_WARN, format); \
|
|
PSpDebug()->DumpInfo(FALSE); \
|
|
} \
|
|
} while (0)
|
|
#define SPDBG_DMSG1(format, arg1) \
|
|
do \
|
|
{ \
|
|
if (PSpDebug()->DumpInfo()) \
|
|
{ \
|
|
_RPT1(_CRT_WARN, format, arg1); \
|
|
PSpDebug()->DumpInfo(FALSE); \
|
|
} \
|
|
} while (0)
|
|
#define SPDBG_DMSG2(format, arg1, arg2) \
|
|
do \
|
|
{ \
|
|
if (PSpDebug()->DumpInfo()) \
|
|
{ \
|
|
_RPT2(_CRT_WARN, format, arg1, arg2); \
|
|
PSpDebug()->DumpInfo(FALSE); \
|
|
} \
|
|
} while (0)
|
|
#define SPDBG_DMSG3(format, arg1, arg2, arg3) \
|
|
do \
|
|
{ \
|
|
if (PSpDebug()->DumpInfo()) \
|
|
{ \
|
|
_RPT3(_CRT_WARN, format, arg1, arg2, arg3); \
|
|
PSpDebug()->DumpInfo(FALSE); \
|
|
} \
|
|
} while (0)
|
|
#define SPDBG_DMSG4(format, arg1, arg2, arg3, arg4) \
|
|
do \
|
|
{ \
|
|
if (PSpDebug()->DumpInfo()) \
|
|
{ \
|
|
_RPT4(_CRT_WARN, format, arg1, arg2, arg3, arg4); \
|
|
PSpDebug()->DumpInfo(FALSE); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define SPDBG_RETURN(hr) \
|
|
{ \
|
|
HRESULT __hr = (hr); \
|
|
if (FAILED(__hr)) \
|
|
{ \
|
|
SPDBG_REPORT_ON_FAIL(__hr); \
|
|
} \
|
|
return __hr; \
|
|
}
|
|
|
|
#define SPDBG_DEBUG_SERVER_ON_START() \
|
|
{ \
|
|
if (PSpDebug()->DebugServerOnStart()) \
|
|
{ \
|
|
if (MessageBox( \
|
|
GetDesktopWindow(), \
|
|
_T("Attach Debugger to the SAPI Server process?"), \
|
|
_T("SAPI"), \
|
|
MB_YESNO) == IDYES) \
|
|
{ \
|
|
USES_CONVERSION; \
|
|
TCHAR szCommand[MAX_PATH + 1]; \
|
|
wsprintf( \
|
|
szCommand, \
|
|
_T("msdev -p %d"), \
|
|
GetCurrentProcessId()); \
|
|
system(T2A(szCommand)); \
|
|
} \
|
|
} \
|
|
}
|
|
|
|
#define SPDBG_DEBUG_CLIENT_ON_START() \
|
|
{ \
|
|
if (PSpDebug()->DebugClientOnStart()) \
|
|
{ \
|
|
TCHAR szModule[MAX_PATH + 1]; \
|
|
szModule[0] = '\0'; \
|
|
TCHAR * pszSapiServer = \
|
|
_T("sapisvr.exe"); \
|
|
GetModuleFileName( \
|
|
NULL, \
|
|
szModule, \
|
|
MAX_PATH); \
|
|
if ((_tcslen(szModule) <= \
|
|
_tcslen(pszSapiServer) || \
|
|
_tcsicmp( \
|
|
szModule + \
|
|
_tcslen(szModule) - \
|
|
_tcslen(pszSapiServer), \
|
|
pszSapiServer) != 0) && \
|
|
MessageBox( \
|
|
GetDesktopWindow(), \
|
|
_T("Attach Debugger to the SAPI Client process?"), \
|
|
_T("SAPI"), \
|
|
MB_YESNO) == IDYES) \
|
|
{ \
|
|
USES_CONVERSION; \
|
|
TCHAR szCommand[MAX_PATH + 1]; \
|
|
wsprintf( \
|
|
szCommand, \
|
|
_T("msdev -p %d"), \
|
|
GetCurrentProcessId()); \
|
|
system(T2A(szCommand)); \
|
|
} \
|
|
} \
|
|
}
|
|
|
|
#else // _DEBUG
|
|
|
|
#define SPDBG_FUNC(name)
|
|
#define SPDBG_REPORT_ON_FAIL(hr)
|
|
#define SPDBG_ASSERT(expr)
|
|
#define SPDBG_VERIFY(expr) (expr)
|
|
#define SPDBG_PMSG0(format)
|
|
#define SPDBG_PMSG1(format, arg1)
|
|
#define SPDBG_PMSG2(format, arg1, arg2)
|
|
#define SPDBG_PMSG3(format, arg1, arg2, arg3)
|
|
#define SPDBG_PMSG4(format, arg1, arg2, arg3, arg4)
|
|
#define SPDBG_DMSG0(format)
|
|
#define SPDBG_DMSG1(format, arg1)
|
|
#define SPDBG_DMSG2(format, arg1, arg2)
|
|
#define SPDBG_DMSG3(format, arg1, arg2, arg3)
|
|
#define SPDBG_DMSG4(format, arg1, arg2, arg3, arg4)
|
|
#define SPDBG_RETURN(hr) return (hr)
|
|
#define SPDBG_DEBUG_SERVER_ON_START()
|
|
#define SPDBG_DEBUG_CLIENT_ON_START()
|
|
|
|
#endif // _DEBUG
|