MFC 中的TRACE开头的宏和ASSERT 用起来挺爽的,可是到了Console 和 Win32下就不好使了,今天正好有雅兴自己也弄几个这样的宏,方便今后使用。
1.支持格式化输出,如Trace0(“%d,%s…)。
2.支持序列化输出,如Trace(0,”abc”<<10<<20.0f<
3.可以设置最高输出等级。例如设置最高输出等级为2,在程序调试过程中只有等级<=2的函数(Trace0,Trace1,Trace2)才可以输出调试信息。
4.支持ASSERT断言宏
5.可以设置输出到日志文件、控制台和调试窗口多种方式。
6.支持多线程。
//debug trace class CDebugTrace { public: CDebugTrace(); ~CDebugTrace(); public: //打印选项 enum enTraceOptions { enTraceLevel = 2, //打印Trace 级别控制 enTraceTimes = 4, //打印时间 enTraceFileAndLine = 8, //打印文件和行号 enTraceToFile = 16, //打印到文件 enTraceToConsole = 32, //打印到控制台 }; public: //开始打印 static CDebugTrace & BeginTrace( CDebugTrace &aDebugTrace,int aiLevel,char *apFilePath,unsigned int auiLine ); //结束打印 static void EndTrace( CDebugTrace & aDebugTrace ); //设置最高可打印级别 static void SetTraceLevel( int aiLevel ); //设置Trace Options, 使用形式: enTraceLevel|enTraceTimes|enTraceFileAndLine static void SetTraceOptions( int aiOptions ); //判断是否可以打印 static BOOL CanTrace( int aiLevel ); //断言失败 static BOOL AssertFail( const char *apstrCond,char *apstrFilePath,unsigned int auiLine ); //设置文件中 static void SetTraceFile(const char *apFileName ); //定义基本数据类型 public: CDebugTrace & operator << ( char apchValue ); CDebugTrace & operator << ( const char *apstrValue ); CDebugTrace & operator << ( const WCHAR *apwstrValue ); CDebugTrace & operator << ( short absValue ); CDebugTrace & operator << ( unsigned short ausValue ); CDebugTrace & operator << ( int aiValue ); CDebugTrace & operator << ( unsigned int auiValue ); CDebugTrace & operator << ( float afValue ); CDebugTrace & operator << ( double adValue ); CDebugTrace & operator << ( long alValue ); CDebugTrace & operator << ( unsigned long aulValue ); CDebugTrace & operator << ( INT64 ai64Value ); CDebugTrace & operator << ( bool abValue ); public: //按格式输出 void TraceFormat( const char *apFormat,... ); private: char m_cbTraceBuffer[TRACE_BUFFER_LEN]; //trace 缓存 int m_iDataLen; //数据长度 static int m_iTraceLevel; //trace 等级 static int m_iTraceOptions; //trace 选项 static char m_chTraceFile[MAX_FILE_PATH]; //文件名 CThreadLock m_ThreadLock; //线程锁 };
//开始打印 CDebugTrace & CDebugTrace::BeginTrace( CDebugTrace &aDebugTrace,int aiLevel,char *apFilePath,unsigned int auiLine ) { ASSERT( apFilePath ); //为线程上锁 aDebugTrace.m_ThreadLock.Lock(); //打印时间 if ( aDebugTrace.m_iTraceOptions&enTraceTimes ) { time_t lcurtime; time(&lcurtime); tm *lptm = localtime(&lcurtime); char lchTime[64] = {0}; _snprintf(lchTime,TRACE_BUFFER_LEN-aDebugTrace.m_iDataLen,/ "[%d-%d %d:%d:%d]----------------/n",lptm->tm_mon+1,lptm->tm_mday,lptm->tm_hour,lptm->tm_min,lptm->tm_sec); aDebugTrace< } //打印级别 if ( aDebugTrace.m_iTraceOptions&enTraceLevel ) { aDebugTrace<<"等级:"< } //打印文件和行号 if ( aDebugTrace.m_iTraceOptions&enTraceFileAndLine ) { aDebugTrace<<"文件:"< } return aDebugTrace; }
跟据配置信息打印各类初始化信息,同时对线程进行加锁。
//按格式输出 void CDebugTrace::TraceFormat( const char *apFormat,... ) { va_list argptr; char *lpTraceBuffer = m_cbTraceBuffer+m_iDataLen; va_start( argptr,apFormat ); m_iDataLen += vsprintf( lpTraceBuffer, apFormat, argptr ); va_end( argptr ); ASSERT( m_iDataLen<= TRACE_BUFFER_LEN ); EndTrace(*this); }
由va_list va_start va_end vsprintf 这系列宏和函数对可变参数进行解析,将格式化的的字符串追加到m_cbTraceBuffer 中。
CDebugTrace & CDebugTrace::operator << ( const char *apstrValue ) { char *lpTraceBuffer = m_cbTraceBuffer+m_iDataLen; if ( apstrValue == NULL ) { m_iDataLen += _snprintf( lpTraceBuffer,TRACE_BUFFER_LEN-m_iDataLen,"NULL" ); } else { m_iDataLen += _snprintf(lpTraceBuffer,TRACE_BUFFER_LEN-m_iDataLen,apstrValue ); } ASSERT( m_iDataLen<= TRACE_BUFFER_LEN ); return *this; }
将传入的数据通过_snprintf 函数格式化后追加到m_cbTraceBuffer 中,下面是数值型的两个函数:
CDebugTrace & CDebugTrace::operator << ( short absValue ) { char *lpTraceBuffer = m_cbTraceBuffer+m_iDataLen; m_iDataLen += _snprintf(lpTraceBuffer,TRACE_BUFFER_LEN-m_iDataLen,"%d",absValue ); ASSERT( m_iDataLen<= TRACE_BUFFER_LEN ); return *this; } CDebugTrace & CDebugTrace::operator << ( double adValue ) { char *lpTraceBuffer = m_cbTraceBuffer+m_iDataLen; m_iDataLen += _snprintf(lpTraceBuffer,TRACE_BUFFER_LEN-m_iDataLen,"%lf",adValue ); ASSERT( m_iDataLen<= TRACE_BUFFER_LEN ); return *this; }
通过实现C++ 每个基本数据类型“<<”操作符重载后,就可以对每个基本数据类型的数据进行序列化操作了。
//结束打印 void CDebugTrace::EndTrace( CDebugTrace & aDebugTrace ) { aDebugTrace<<"/n"; #ifdef _DEBUG //输出到调试窗口 OutputDebugStringA( aDebugTrace.m_cbTraceBuffer ); #endif //打印到文件 if ( m_iTraceOptions&enTraceToFile ) { FILE *lpFile = fopen(m_chTraceFile,"a"); if ( lpFile ) { fprintf(lpFile,aDebugTrace.m_cbTraceBuffer); fclose(lpFile); } } //打印到控制台 if ( m_iTraceOptions&enTraceToConsole ) { printf(aDebugTrace.m_cbTraceBuffer); } aDebugTrace.m_iDataLen = 0; aDebugTrace.m_ThreadLock.UnLock(); }
跟据配置信息打印 m_cbTraceBuffer 中的数据,重置数据下次使用,并对线程解锁。
//断言失败 BOOL CDebugTrace::AssertFail( const char *apstrCond,char *apstrFilePath,unsigned int auiLine ) { char lchMessage[256] = {0}; #ifdef _DEBUG //弹出断言框 char lchFilePath[256] = {0}; ::GetModuleFileNameA(NULL,lchFilePath,sizeof(lchFilePath)); _snprintf(lchMessage,sizeof(lchMessage),/ "断言失败!/n/n程序:%s/n文件:%s/n行号:%u/n表达式:%s/n/n请查看日志文件或者编译器输出窗口的调试信息。/n/n选择/"重试/"调试程序",/ lchFilePath,apstrFilePath,auiLine,apstrCond ); int liResult = MessageBoxA(NULL,lchMessage,"断言",MB_ABORTRETRYIGNORE|MB_ICONSTOP|MB_DEFBUTTON1); if ( liResult==IDABORT ) { FatalExit(-1); } else if( liResult==IDRETRY) { return FALSE; } #else _snprintf(lchMessage,sizeof(lchMessage),/ "断言失败!/n/n文件:%s/n行号:%u/n表达式:%s/n,/ lchFilePath,apstrFilePath,auiLine,apstrCond ); #endif return TRUE; }
处理断言失败,弹出类似于MFC 中的断言框,不过上面的文字是中文的。^_^
通过上面的代码不难看出来要打印一条信息最主要的几步是先调用下BeginTrace 函数,然后调用TraceFormat 或者重载“<<”操作符的函数,将数据依次追加到 m_cbTraceBuffer 中。最终调用EndTrace 将m_cbTraceBuffer 中的数据打印出来,下面看下如何将这一系列操作定义成一个宏。
#define TraceLog0 / if ( CDebugTrace::CanTrace(0) )/ CDebugTrace::BeginTrace(g_DebugTrace,0,__FILE__,__LINE__).TraceFormat #ifdef _DEBUG #define TRACE0 TraceLog0 #endif
#ifdef _DEBUG #define ASSERT(f) { if( !(f) ) / {/ if (!CDebugTrace::AssertFail(#f,__FILE__,__LINE__))/ {/ DebugBreak();/ }/ } / } #endif
CDebugTrace 类需要在工程中定义成全局变量来方便使用,并确保在调用到的cpp中“extern” 了全局变量。需要注意的是所有的字符操作都是调用ANSI 版的函数完成的,所以在使用的过程中不要出现“Trace0(TEXT(“%d,%s”),2,…);”或 “Trace0(_T(“%d,%s”),2,_T(“test”));”带有“TEXT”和“_T”宏的使用方式。