浅析ASSERT和TRACE宏附源代码转.docx

上传人:b****6 文档编号:8474449 上传时间:2023-01-31 格式:DOCX 页数:10 大小:22.56KB
下载 相关 举报
浅析ASSERT和TRACE宏附源代码转.docx_第1页
第1页 / 共10页
浅析ASSERT和TRACE宏附源代码转.docx_第2页
第2页 / 共10页
浅析ASSERT和TRACE宏附源代码转.docx_第3页
第3页 / 共10页
浅析ASSERT和TRACE宏附源代码转.docx_第4页
第4页 / 共10页
浅析ASSERT和TRACE宏附源代码转.docx_第5页
第5页 / 共10页
点击查看更多>>
下载资源
资源描述

浅析ASSERT和TRACE宏附源代码转.docx

《浅析ASSERT和TRACE宏附源代码转.docx》由会员分享,可在线阅读,更多相关《浅析ASSERT和TRACE宏附源代码转.docx(10页珍藏版)》请在冰豆网上搜索。

浅析ASSERT和TRACE宏附源代码转.docx

浅析ASSERT和TRACE宏附源代码转

如果你没有用过甚至听过ASSERT或者TRACE调式宏,那么在很大程度上,你可以忽略这篇文章。

不再扯不相关的东西,我们直入主题好了。

1.TRACE

1.1.TRACE的宏定义

  同样的,我们先从TRACE的宏定义开始研究,TRACE被定义在AFX.H中。

但是我在这个H文件查找时,并没有发现TRACE被#define成某个函数。

虽然你会发现类似的下面两行代码:

#defineTRACE          __noop

///////////////////////////////////

#defineTRACEATLTRACE

但是,ATL的宏定义并不是我们要找的,而__noop,如果你翻过MSDN会发现这个Keyword的作用仅仅是忽略被包含的函数及不对参数进行评估(具体请看A附录)。

  那么,TRACE到底是如何被使用的呢?

机缘巧合之下(这个……),我在TRACE的宏定义附近发现了下面的代码:

inlinevoidAFX_CDECLAfxTrace(...){}  //Lookathere!

#defineTRACE          __noop

#defineTRACE0(sz)

#defineTRACE1(sz,p1)

#defineTRACE2(sz,p1,p2)

#defineTRACE3(sz,p1,p2,p3)

关注那个AfxTrace。

如果说我们前面找到的都不是真正的TRACE的话,那么,这个AfxTrace就非常可能是我们要找的,而且,他还是个inline函数!

于是,我以“AfxTrace”为关键字Google,果然找到了一些信息。

  在以前的AFX.H文件中,存在类似下面的代码:

#ifdef  _DEBUG  

  void  AFX_CDECL  AfxTrace(LPCTSTR  lpszFormat,  ...);  

  #define  TRACE                    :

:

AfxTrace  

#else  

  #define  TRACE      1  ?

  (void)0  :

  :

:

AfxTrace  

#endif

很明显,我们可以看到,TRACE被定义成了AfxTrace。

但是这里有个问题,为什么在我的VC2003的AFX.H文件中,找不到这段代码呢?

看来需要高人回答哈~

1.2.AfxTrace

  既然TRACE宏只是调用了AfxTrace,那么我们就来看看AfxTrace函数实现了什么。

不过很可惜,AfxTrace并不是一个文档记录函数(Documented-function),这意味着你在MSDN是找不到他的相关信息,那么我们只能通过他的源代码来了解他的行为。

AfxTrace的源代码在DUMPOUT.CPP里。

voidAFX_CDECLAfxTrace(LPCTSTRlpszFormat,...)

{

  va_listargs;

  va_start(args,lpszFormat);

  intnBuf;

  TCHARszBuffer[512];

  nBuf=_vsntprintf(szBuffer,_countof(szBuffer),lpszFormat,args);

  //wasthereanerror?

wastheexpandedstringtoolong?

  ASSERT(nBuf>=0);

  afxDump<

  va_end(args);

}

首先我们来看三个东西:

va_listargs;

va_start(args,lpszFormat);

va_end(args);

这一组宏主要是用来解决函数的不定参数的。

回想一下,我们可以在TRACE中输出任意个参数,靠的就是这三个东西。

因为C没有重载函数,更何况即使有,当函数参数个数不确定时,重载的局限性就显得非常大,于是就有人想通过利用指针参数解决了这个问题。

  由于这个东西比较复杂(MS足够再写一篇专门的文章了),所以在此不作详细阐述,有兴趣的可以参考下列URL:

1.MSDN:

2.

  接着我们可以看到,AfxTrace声明了大小为512的TCHAR数组作为缓冲区,然后用_vsntprintf往缓冲区里写已经格式化好的数据。

  而_vsntprintf系列函数专门用于以va_list处理可变参数的函数输出。

具体请参考附录

  最后,AfxTrace又用afxDump对szBuffer进行转储(似乎就是输出到输出框)。

看来,我们还需要对afxDump函数进行跟进。

1.3.afxDump

  afxDump是一个CDumpContext类的预定义对象,用于在Debug模式下,往VC的输出窗口输出调试信息。

很幸运,M$在MSDN中记录了他的一些信息。

(具体请参考附录)

  所以,一般的,当存在如下代码时:

  LPCTSTRlpszKC=L"KCisaFucker";

  afxDump<

输出框便会输出“KCisafucker”。

  我们现在来看看afxDump的定义代码。

源代码在AFX.H中

#ifdef_DEBUG

externAFX_DATACDumpContextafxDump;  //LookAtHere!

externAFX_DATABOOLafxTraceEnabled;  //这个变量和afxTraceFlags同为调式输出的开关标志

                          //不过MS在新版本的MFC中被废除了

#endif

然后我们再来看看CDumpContext辅助类的定义。

源代码同样也在AFX.H中

classCDumpContext

{

public:

  CDumpContext(CFile*pFile=NULL);

//Attributes

  intGetDepth()const;    //0=>thisobject,1=>childrenobjects

  voidSetDepth(intnNewDepth);

//Operations

  CDumpContext&operator<<(LPCTSTRlpsz);

#ifdef_UNICODE

  CDumpContext&operator<<(LPCSTRlpsz);  //automaticallywidened

#else

  CDumpContext&operator<<(LPCWSTRlpsz);//automaticallythinned

#endif

  CDumpContext&operator<<(constvoid*lp);

  CDumpContext&operator<<(constCObject*pOb);

  CDumpContext&operator<<(constCObject&ob);

  CDumpContext&operator<<(BYTEby);

  CDumpContext&operator<<(WORDw);

  CDumpContext&DumpAsHex(BYTEb);

  CDumpContext&DumpAsHex(WORDw);

#ifdef_WIN64

  CDumpContext&operator<<(LONGl);

  CDumpContext&operator<<(DWORDdw);

  CDumpContext&operator<<(intn);

  CDumpContext&operator<<(UINTu);

  CDumpContext&DumpAsHex(LONGl);

  CDumpContext&DumpAsHex(DWORDdw);

  CDumpContext&DumpAsHex(intn);

  CDumpContext&DumpAsHex(UINTu);

#else

  CDumpContext&operator<<(LONG_PTRl);

  CDumpContext&operator<<(DWORD_PTRdw);

  CDumpContext&operator<<(INT_PTRn);

  CDumpContext&operator<<(UINT_PTRu);

  CDumpContext&DumpAsHex(LONG_PTRl);

  CDumpContext&DumpAsHex(DWORD_PTRdw);

  CDumpContext&DumpAsHex(INT_PTRn);

  CDumpContext&DumpAsHex(UINT_PTRu);

#endif

  CDumpContext&operator<<(floatf);

  CDumpContext&operator<<(doubled);

  CDumpContext&operator<<(LONGLONGn);

  CDumpContext&operator<<(ULONGLONGn);

  CDumpContext&DumpAsHex(LONGLONGn);

  CDumpContext&DumpAsHex(ULONGLONGn);

  CDumpContext&operator<<(HWNDh);

  CDumpContext&operator<<(HDCh);

  CDumpContext&operator<<(HMENUh);

  CDumpContext&operator<<(HACCELh);

  CDumpContext&operator<<(HFONTh);

  voidHexDump(LPCTSTRlpszLine,BYTE*pby,intnBytes,intnWidth);

  voidFlush();

//Implementation

protected:

  //dumpcontextobjectscannotbecopiedorassigned

  CDumpContext(constCDumpContext&dcSrc);

  voidoperator=(constCDumpContext&dcSrc);

  voidOutputString(LPCTSTRlpsz);

  intm_nDepth;

public:

  CFile*m_pFile;

};

CDumpContext只有一个构造函数,而且默认把m_pFile设置成了NULL,这点很关键,我们在后面马上会看到~

  这里可能会有点疑问,为什么会存在一个CFile*类型的Public成员变量?

我也不知道,KC个人的猜测是,CDumpContext不仅能够往输出框输出信息,应该还能够往外写文件。

而下面的m_pFile->Write也能够支持我的猜测。

  另一个亮点是,CDumpContext中存在多个<<重载运算符,这样便于afxDump进行不同类型的<<运算。

不过这里有一个插曲,CDumpContext的上述代码中有几行比较有意思:

//Operations

  CDumpContext&operator<<(LPCTSTRlpsz);

#ifdef_UNICODE

  CDumpContext&operator<<(LPCSTRlpsz);  //automaticallywidened

#else

  CDumpContext&operator<<(LPCWSTRlpsz);//automaticallythinned

#endif

之前我一直不明白这段的用意,后来经D大提醒,幡然醒悟。

  这段宏的作用大致是:

在UNICODE下,遇到MBCS字符串自动做扩大处理;在MBCS下,遇到UNICODE字符串自动做缩小处理。

相应的实现代码如下:

#ifdef_UNICODE

//specialversionforANSIcharacters

CDumpContext&CDumpContext:

:

operator<<(LPCSTRlpsz)

{

  if(lpsz==NULL)

  {

  OutputString(L"(NULL)");

  return*this;

  }

  //limitedlength

  TCHARszBuffer[512];

  _mbstowcsz(szBuffer,lpsz,_countof(szBuffer));

  szBuffer[511]=0;

  return*this<

}

#else  //_UNICODE

//specialversionforWIDEcharacters

CDumpContext&CDumpContext:

:

operator<<(LPCWSTRlpsz)

{

  if(lpsz==NULL)

  {

  OutputString("(NULL)");

  return*this;

  }

  //limitedlength

  charszBuffer[512];

  _wcstombsz(szBuffer,lpsz,_countof(szBuffer));

  szBuffer[511]=0;

  return*this<

}

#endif  //!

_UNICODE

/////////////////////////////////////////////////////////////////////////////

接下来我们重点看CDumpContext对<<的实现。

虽然<<的重载很多,但是从本质上,可以分成对String和数值类型的两类。

那么我们先来看看对于数值类型的处理,额,随便挑一个~当~当~当~当~

CDumpContext&CDumpContext:

:

operator<<(WORDw)

{

  TCHARszBuffer[32];

  wsprintf(szBuffer,_T("%u"),(UINT)w);

  OutputString(szBuffer);

  return*this;

}

因为是数值类型,所以算上64Bit的大整数,也长不到哪里去。

所以这里分配的缓冲区数组的下标只有32.

  然后利用wsprintf把数字格式化,最后用OutputString输出。

wsprintf详细信息请参考附录

  至于对String的处理,代码如下:

CDumpContext&CDumpContext:

:

operator<<(LPCTSTRlpsz)

{

  if(lpsz==NULL)

  {

  OutputString(_T("NULL"));

  return*this;

  }

  ASSERT(lpsz!

=NULL);

  if(lpsz==NULL)

  AfxThrowUserException();

  if(m_pFile==NULL)

  {

  TCHARszBuffer[512];

  LPTSTRlpBuf=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;

}

做<<前,先对参数进行合法性检查,然后在m_pFile为NULL的情况下,分配缓冲区(由于是字符串,所以下标为512),然后逐一的复制字符,最后相同的用OutputString转出。

  比较上面两种<<的运算实现,我们可以很明显的看出,最后的数据都被传递到了OutputString里,所以我们还必须跟进OutputString。

1.4.OutputString

  我们现在跳到OutputString的实现源代码上:

voidCDumpContext:

:

OutputString(LPCTSTRlpsz)

{

  //useC-runtime/OutputDebugStringwhenm_pFileisNULL

  if(m_pFile==NULL)

  {

  TRACE(traceDumpContext,0,lpsz);

  return;

  }

  ASSERT(lpsz!

=NULL);

  if(lpsz==NULL)

  AfxThrowUserException();

  //otherwise,writethestringtothefile

  m_pFile->Write(lpsz,lstrlen(lpsz)*sizeof(TCHAR));

}

因为前面说过,m_pFile的值为NULL(我们没有给他传值,构造函数又自动给他NULL掉了),所以OutputString应该会执行下面的代码:

//useC-runtime/OutputDebugStringwhenm_pFileisNULL

  if(m_pFile==NULL)

  {

  TRACE(traceDumpContext,0,lpsz);

  return;

  }

很奇怪,很神奇,很……囧……又回到了TRACE……

  更何况,上面注释写着useC-runtime/OutputDebugString的字眼呢,多大个的字啊……

  无奈中,我去翻了下MSDN,又去Google,结果得到了惊人的发现!

在MSDN,对于CDumpContext有这么一段的描述:

UndertheWindowsenvironment,theoutputfromthepredefinedafxDumpobject,conceptuallysimilartothecerrstream,isroutedtothedebuggerviatheWindowsfunctionOutputDebugString.

换句话说,转储的东西的的确确会经过底层的C运行时库函数或者OutputDebugString这个API。

  此时我想起了之前出现的一个关于TRACE的BUG:

在UNICODE下无法输出中文。

当时我通过F9/F10/F11不断的跟进,但是单语句调试到TRACE(traceDumpContext,0,lpsz)这里时,却提示没有可显示的语句。

所以,有可能转储的东西跑到了某个C底层函数去(如果是OutputDebugString,中文也应输出)。

  于是我把目光瞄准了traceDumpContext,发现这个是个宏(很奇怪,是ATL系列的),经过多次进进出出的跟进后,发现了一个叫做CTrace的类,而且在里面还发现如下代码:

classCTrace

{

public:

  typedefint(__cdecl*fnCrtDbgReport_t)(int,constchar*,int,constchar*,constchar*,...);

private:

  CTrace(

#ifdef_ATL_NO_DEBUG_CRT

  fnCrtDbgReport_tpfnCrtDbgReport=NULL)

#else

  fnCrtDbgReport_tpfnCrtDbgReport=_CrtDbgReport)

#endif

我很敏感的关注了_CtrDbgReport这个函数,去MSDN翻了下,得到的结果很惊人!

Generatesareportwithadebuggingmessageandsendsthereporttothreepossibledestinations(debugversiononly).

而且,Remark上还有这么一段(具体请参考附录):

InVisualC++2005,_CrtDbgReportWisthewide-characterversionof_CrtDbgReport.Allitsoutputandstringparametersareinwide-characterstrings;otherwiseitisidenticaltothesingle-bytecharacterversion.

_CrtDbgReportand_CrtDbgReportWcreatetheusermessageforthedebugreportbysubstitutingtheargument[n]argumentsintotheformatstring,usingthesamerulesdefinedbytheprintforwprintffunctions.Thesefunctionsthengeneratethedebugreportanddeterminethedestinationordestinations,basedonthecurrentreportmodesandfiledefinedforreportType.Whenth

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 求职职场 > 社交礼仪

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1