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~