淺析ASSERT&TRACE宏
1.TRACE
1.1.TRACE的宏定義
同樣的,我們先從TRACE的宏定義開始研究,TRACE被定義在AFX.H中。但是我在這個H文件查找時,並沒有發現TRACE被#define成某個函數。雖然你會發現類似的下面兩行代碼:
引用:
#define TRACE __noop
///////////////////////////////////
#define TRACE ATLTRACE
那麼,TRACE到底是如何被使用的呢?機緣巧合之下(這個……),我在TRACE的宏定義附近發現了下面的代碼:
引用:
inline void AFX_CDECL AfxTrace(...) { } // Look at here!
#define TRACE __noop
#define TRACE0(sz)
#define TRACE1(sz, p1)
#define TRACE2(sz, p1, p2)
#define TRACE3(sz, p1, p2, p3)
在以前的AFX.H文件中,存在類似下面的代碼:
引用:
#ifdef _DEBUG
void AFX_CDECL AfxTrace(LPCTSTR lpszFormat, ...);
#define TRACE ::AfxTrace
#else
#define TRACE 1 ? (void)0 : ::AfxTrace
#endif
1.2.AfxTrace
既然TRACE宏只是調用了AfxTrace,那麼我們就來看看AfxTrace函數實現了什麼。不過很可惜,AfxTrace並不是一個文檔記錄函數(Documented-function),這意味着你在MSDN是找不到他的相關信息,那麼我們只能通過他的源代碼來了解他的行爲。AfxTrace的源代碼在DUMPOUT.CPP裏。
引用:
void AFX_CDECL AfxTrace(LPCTSTR lpszFormat, ...)
{
va_list args;
va_start(args, lpszFormat);
int nBuf;
TCHAR szBuffer[512];
nBuf = _vsntprintf(szBuffer, _countof(szBuffer), lpszFormat, args);
// was there an error? was the expanded string too long?
ASSERT(nBuf >= 0);
afxDump << szBuffer;
va_end(args);
}
引用:
va_list args;
va_start(args, lpszFormat);
va_end(args);
由於這個東西比較複雜(MS足夠再寫一篇專門的文章了),所以在此不作詳細闡述,有興趣的可以參考下列URL:
1.MSDN:http://msdn.microsoft.com/en-us/library/kb57fad8.aspx
2.http://www.cppblog.com/qiujian5628/archive/2008/01/21/41562.html
接着我們可以看到,AfxTrace聲明瞭大小爲512的TCHAR數組作爲緩衝區,然後用_vsntprintf往緩衝區裏寫已經格式化好的數據。
而_vsntprintf系列函數專門用於以va_list處理可變參數的函數輸出。具體請參考附錄
最後,AfxTrace又用afxDump對szBuffer進行轉儲(似乎就是輸出到輸出框)。看來,我們還需要對afxDump函數進行跟進。
1.3.afxDump
afxDump是一個CDumpContext類的預定義對象,用於在Debug模式下,往VC的輸出窗口輸出調試信息。很幸運,M$在MSDN中記錄了他的一些信息。(具體請參考附錄)
所以,一般的,當存在如下代碼時:
引用:
LPCTSTR lpszKC = L"KC is a Fucker";
afxDump << lpszKC;
我們現在來看看afxDump的定義代碼。源代碼在AFX.H中
引用:
#ifdef _DEBUG
extern AFX_DATA CDumpContext afxDump; // Look At Here!
extern AFX_DATA BOOL afxTraceEnabled; // 這個變量和afxTraceFlags同爲調式輸出的開關標誌
// 不過MS在新版本的MFC中被廢除了
#endif
引用:
class CDumpContext
{
public:
CDumpContext(CFile* pFile = NULL);
// Attributes
int GetDepth() const; // 0 => this object, 1 => children objects
void SetDepth(int nNewDepth);
// Operations
CDumpContext& operator<<(LPCTSTR lpsz);
#ifdef _UNICODE
CDumpContext& operator<<(LPCSTR lpsz); // automatically widened
#else
CDumpContext& operator<<(LPCWSTR lpsz); // automatically thinned
#endif
CDumpContext& operator<<(const void* lp);
CDumpContext& operator<<(const CObject* pOb);
CDumpContext& operator<<(const CObject& ob);
CDumpContext& operator<<(BYTE by);
CDumpContext& operator<<(WORD w);
CDumpContext& DumpAsHex(BYTE b);
CDumpContext& DumpAsHex(WORD w);
#ifdef _WIN64
CDumpContext& operator<<(LONG l);
CDumpContext& operator<<(DWORD dw);
CDumpContext& operator<<(int n);
CDumpContext& operator<<(UINT u);
CDumpContext& DumpAsHex(LONG l);
CDumpContext& DumpAsHex(DWORD dw);
CDumpContext& DumpAsHex(int n);
CDumpContext& DumpAsHex(UINT u);
#else
CDumpContext& operator<<(LONG_PTR l);
CDumpContext& operator<<(DWORD_PTR dw);
CDumpContext& operator<<(INT_PTR n);
CDumpContext& operator<<(UINT_PTR u);
CDumpContext& DumpAsHex(LONG_PTR l);
CDumpContext& DumpAsHex(DWORD_PTR dw);
CDumpContext& DumpAsHex(INT_PTR n);
CDumpContext& DumpAsHex(UINT_PTR u);
#endif
CDumpContext& operator<<(float f);
CDumpContext& operator<<(double d);
CDumpContext& operator<<(LONGLONG n);
CDumpContext& operator<<(ULONGLONG n);
CDumpContext& DumpAsHex(LONGLONG n);
CDumpContext& DumpAsHex(ULONGLONG n);
CDumpContext& operator<<(HWND h);
CDumpContext& operator<<(HDC h);
CDumpContext& operator<<(HMENU h);
CDumpContext& operator<<(HACCEL h);
CDumpContext& operator<<(HFONT h);
void HexDump(LPCTSTR lpszLine, BYTE* pby, int nBytes, int nWidth);
void Flush();
// Implementation
protected:
// dump context objects cannot be copied or assigned
CDumpContext(const CDumpContext& dcSrc);
void operator=(const CDumpContext& dcSrc);
void OutputString(LPCTSTR lpsz);
int m_nDepth;
public:
CFile* m_pFile;
};
這裏可能會有點疑問,爲什麼會存在一個CFile*類型的Public成員變量?我也不知道,KC個人的猜測是,CDumpContext不僅能夠往輸出框輸出信息,應該還能夠往外寫文件。而下面的m_pFile->Write也能夠支持我的猜測。
另一個亮點是,CDumpContext中存在多個<<重載運算符,這樣便於afxDump進行不同類型的<<運算。不過這裏有一個插曲,CDumpContext的上述代碼中有幾行比較有意思:
引用:
// Operations
CDumpContext& operator<<(LPCTSTR lpsz);
#ifdef _UNICODE
CDumpContext& operator<<(LPCSTR lpsz); // automatically widened
#else
CDumpContext& operator<<(LPCWSTR lpsz); // automatically thinned
#endif
這段宏的作用大致是:在UNICODE下,遇到MBCS字符串自動做擴大處理;在MBCS下,遇到UNICODE字符串自動做縮小處理。相應的實現代碼如下:
引用:
#ifdef _UNICODE
// special version for ANSI characters
CDumpContext& CDumpContext::operator<<(LPCSTR lpsz)
{
if (lpsz == NULL)
{
OutputString(L"(NULL)");
return *this;
}
// limited length
TCHAR szBuffer[512];
_mbstowcsz(szBuffer, lpsz, _countof(szBuffer));
szBuffer[511] = 0;
return *this << szBuffer;
}
#else //_UNICODE
// special version for WIDE characters
CDumpContext& CDumpContext::operator<<(LPCWSTR lpsz)
{
if (lpsz == NULL)
{
OutputString("(NULL)");
return *this;
}
// limited length
char szBuffer[512];
_wcstombsz(szBuffer, lpsz, _countof(szBuffer));
szBuffer[511] = 0;
return *this << szBuffer;
}
#endif //!_UNICODE
/////////////////////////////////////////////////////////////////////////////
引用:
CDumpContext& CDumpContext::operator<<(WORD w)
{
TCHAR szBuffer[32];
wsprintf(szBuffer, _T("%u"), (UINT) w);
OutputString(szBuffer);
return *this;
}
然後利用wsprintf把數字格式化,最後用OutputString輸出。wsprintf詳細信息請參考附錄
至於對String的處理,代碼如下:
引用:
CDumpContext& CDumpContext::operator<<(LPCTSTR lpsz)
{
if (lpsz == NULL)
{
OutputString(_T("NULL"));
return *this;
}
ASSERT( lpsz != NULL );
if( lpsz == NULL )
AfxThrowUserException();
if (m_pFile == NULL)
{
TCHAR szBuffer[512];
LPTSTR lpBuf = szBuffer;
while (*lpsz != '/0')
{
if (lpBuf > szBuffer + _countof(szBuffer) - 3)
{
*lpBuf = '/0';
OutputString(szBuffer);
lpBuf = szBuffer;
}
if (*lpsz == '/n')
*lpBuf++ = '/r';
*lpBuf++ = *lpsz++;
}
*lpBuf = '/0';
OutputString(szBuffer);
return *this;
}
m_pFile->Write(lpsz, lstrlen(lpsz)*sizeof(TCHAR));
return *this;
}
比較上面兩種<<的運算實現,我們可以很明顯的看出,最後的數據都被傳遞到了OutputString裏,所以我們還必須跟進OutputString。
1.4.OutputString
我們現在跳到OutputString的實現源代碼上:
引用:
void CDumpContext::OutputString(LPCTSTR lpsz)
{
// use C-runtime/OutputDebugString when m_pFile is NULL
if (m_pFile == NULL)
{
TRACE(traceDumpContext, 0, lpsz);
return;
}
ASSERT( lpsz != NULL );
if( lpsz == NULL )
AfxThrowUserException();
// otherwise, write the string to the file
m_pFile->Write(lpsz, lstrlen(lpsz)*sizeof(TCHAR));
}
引用:
// use C-runtime/OutputDebugString when m_pFile is NULL
if (m_pFile == NULL)
{
TRACE(traceDumpContext, 0, lpsz);
return;
}
更何況,上面註釋寫着use C-runtime/OutputDebugString的字眼呢,多大個的字啊……
無奈中,我去翻了下MSDN,又去Google,結果得到了驚人的發現!在MSDN,對於CDumpContext有這麼一段的描述:
引用:
Under the Windows environment, the output from the predefined afxDump object, conceptually similar to the cerr stream, is routed to the debugger via the Windows function OutputDebugString.
此時我想起了之前出現的一個關於TRACE的BUG:在UNICODE下無法輸出中文。當時我通過F9/F10/F11不斷的跟進,但是單語句調試到TRACE(traceDumpContext, 0, lpsz)這裏時,卻提示沒有可顯示的語句。所以,有可能轉儲的東西跑到了某個C底層函數去(如果是OutputDebugString,中文也應輸出)。
於是我把目光瞄準了traceDumpContext,發現這個是個宏(很奇怪,是ATL系列的),經過多次進進出出的跟進後,發現了一個叫做CTrace的類,而且在裏面還發現如下代碼:
引用:
class CTrace
{
public:
typedef int (__cdecl *fnCrtDbgReport_t)(int,const char *,int,const char *,const char *,...);
private:
CTrace(
#ifdef _ATL_NO_DEBUG_CRT
fnCrtDbgReport_t pfnCrtDbgReport = NULL)
#else
fnCrtDbgReport_t pfnCrtDbgReport = _CrtDbgReport)
#endif
引用:
Generates a report with a debugging message and sends the report to three possible destinations (debug version only).
引用:
In Visual C++ 2005, _CrtDbgReportW is the wide-character version of _CrtDbgReport. All its output and string parameters are in wide-character strings; otherwise it is identical to the single-byte character version.
_CrtDbgReport and _CrtDbgReportW create the user message for the debug report by substituting the argument[n] arguments into the format string, using the same rules defined by the printf or wprintf functions. These functions then generate the debug report and determine the destination or destinations, based on the current report modes and file defined for reportType. When the report is sent to a debug message window, the filename, lineNumber, and moduleName are included in the information displayed in the window.
OutputString通過宏定義,最終轉到了_CtrDbgReport這個C-Runtime函數上,有他負責在底層的某個地方往輸出框寫調試信息。
又因爲我的IDE是2003,所以_CtrDbgReport只能輸出Single-byte Char,所以無法輸出中文。
如此,我們的淺析TRACE的部分應該算是圓滿結束了-。-||
2.ASSERT
看完了TRACE,我們再來看看ASSERT。相對來說,ASSERT就比TRACE簡單很多,所以我們不再分佈敘述~
老規矩,還是先從宏定義開始入手。源代碼在AFX.H中
引用:
#define ASSERT(f) (void) ((f) || !AfxAssertFailedLine(THIS_FILE, __LINE__) || (AfxDebugBreak(), 0))
當f爲FALSE的時候,會跳轉到AfxAssertFailedLine,這個函數的源代碼在AFXASERT.CPP中
引用:
BOOL AFXAPI AfxAssertFailedLine(LPCSTR lpszFileName, int nLine)
{
#ifndef _AFX_NO_DEBUG_CRT
// we remove WM_QUIT because if it is in the queue then the message box
// won't display
MSG msg;
BOOL bQuit = PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE);
BOOL bResult = _CrtDbgReport(_CRT_ASSERT, lpszFileName, nLine, NULL, NULL);
if (bQuit)
PostQuitMessage((int)msg.wParam);
return bResult;
#else
// Not supported.
#error _AFX_NO_DEBUG_CRT is not supported.
#endif // _AFX_NO_DEBUG_CRT
}
這個函數成功後會返回TRUE,經過!運算後就變成了FALSE,於是繼續執行AfxDebugBreak。
AfxDebugBreak是一個系列宏定義,源代碼在AFXVER_.H中
引用:
#ifndef AfxDebugBreak
#ifdef _AFX_NO_DEBUG_CRT
// by default, debug break is asm int 3, or a call to DebugBreak, or nothing
#if defined(_M_IX86) && !defined(_AFX_PORTABLE)
#define AfxDebugBreak() _asm { int 3 }
#else
#define AfxDebugBreak() DebugBreak()
#endif
#else
#define AfxDebugBreak() _CrtDbgBreak()
#endif
#endif
#ifndef _DEBUG
#ifdef AfxDebugBreak
#undef AfxDebugBreak
#endif
#define AfxDebugBreak()
#endif // _DEBUG
3.尾聲
好了,終於可以說End Up了~這個算是KC放假後做的第一個Project吧,雖然不具有實際價值-_-!。其實我很早之前就像研究這兩個宏了,只是當時沒有時間。
之前被TRACE在UNICODE下無法輸出中文的問題困擾了好久,一直說要研究SRC弄清楚原因,現在總算可以給自己交上一份答卷了~HOHO~
最後感謝支持我的各位,感謝MDSA Group、JAFT、CFAN的各位~