对于已有的工程,首先把exception.h和exception.cpp加入工程,把原来的自定义的异常类继承自CMyException,然后同上的方法捕获异常,每个线程入口增加初始化函数即可,可以与你原来的异常处理完美集成。 对于在MFC中的用法,可以按如下方式捕获异常: try { vTest(); } catch ( const exception& e ) { cout << e.what() << endl; } // CException是MFC中异常基类,MFC中的异常通常从堆中分配,所以应通过指针捕获,// 而且使用完之后还应该调用delete函数清除内存。 catch ( CException* e ) { // hadle exception e->delete(); } 即在MFC异常上加一层捕获标准C++异常和结构化异常以及自定义异常。另外由于MFC中已经自动有了处理内存泄露的机制,所以需要删除exception.h文件的第34行到第40行(有关内存泄露的说明见下面),由于在MFC中每个.cpp文件开始处都要包含stdafx.h,所以还需要在exception.cpp文件开始处加上#include “stdafx.h”,不然会编译不通过。 如果你希望把堆栈信息输出在文件中,以防丢失,可以使用IO重定向功能(有关IO重定向可参考Jim Hyslop and Herb Sutter的文章 http://www.cuj.com/experts/1903/hyslop.htm ),即在main()函数开头加入以下语句: ofstream ofLog( "exception.txt", ios_base::app ); streambuf *outbuf = cout.rdbuf( ofLog.rdbuf() ); 这样,所有输出到console的信息就重定向到exception.txt文件中了。如果你想恢复,则可以加入以下语句: // restore the buffers cout.rdbuf( outbuf ); 对于release版本,如果你运行,你会发现程序不能捕获非法内存访问、除零等结构化异常,这是因为VC在release版默认是同步异常,不捕获结构化异常,只能捕获C++的异常,所以你需要修改编译选项,采用异步异常模型,在project->setting->c/c++->project options框中增加/EHa的编译选项。另外,release版默认不生成调试符号文件,这样你就不能不能打印出抛出异常的代码的行号等信息,所以你需要修改编译配置,方法如下:Project->Settings->c/c++页中的debug info列表选项中选择program database项。这样release版本也能实现堆栈跟踪。当然,这样会使release版本减慢速度,而且还要带一个debug info文件,因为有些bug只有在release版本中才会出现,而且release版是真正给客户使用的,所以必须测试release版,可以考虑release的beta1和beta2版本带这些调试信息,这样的话,因为debug版和release版都测试通过,发行给客户的最终正式版可以通过设置一个宏注释掉这些调试信息,恢复成同步异常模型,即恢复成VC默认的release版配置。 4. 其他需要注意的问题 本类库还有检查内存泄露的功能,只要你在每个.cpp文件的所有#include之后,加上以下语句: // 以下几行是能够定义到发生内存泄露的代码行。在每个.cpp文件都应该声明。 #include “Exception.h” #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif 然后以debug方式启动程序,当程序正常退出或关闭时(注意不能用stop debug的命令停止,否则将不会打印出内存泄露的信息),在VC的debug窗口将会打印出有可能产生内存泄露的源代码信息,包括文件名和行号。由于MFC程序自动会生成这些代码,所以在MFC程序中不需要手工添加这些代码。例如,当你以debug方式运行下载的测试程序,当程序正常退出后,在debug窗口会显示如下语句: Detected memory leaks! Dumping objects -> H:C++ TestStackWalkTestmain.cpp(88) : {184} normal block at 0x00632D50, 100 bytes long. Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD Object dump complete. 从上面第3行可以知道在main.cpp文件的第88行产生了内存泄露,找到这一行就是 char* pcLeak = new char[ 100 ]; 检查上下文,发现果然没有释放内存。 当然如同你使用Purify、BoundsChecker等工具检查内存泄露一样,它也会谎报军情,有些不会内存泄露的地方,它也告诉你内存泄露了,尤其当你使用了大量STL类库时,这就需要你细心检查上下文,以确定是否是内存泄露了。 本类库由于使用了一些VC中特有调试符号特性,所以可能不能在其它编译器下通过。另外,本文讨论的堆栈跟踪实现都是基于Windows 2000以上,Win98和Win95将不能输出导致抛出异常的语句所在的文件名和行号。本类库也不能在Unix或Linux下运行。有在Unix或Linux平台工作的读者朋友如果有兴趣,可以实现一个在Unix或Linux平台运行的C++异常堆栈跟踪类库。 本类库不能跟踪标准C++异常及其它你不能控制的异常的堆栈信息,即当这些异常抛出时,不能输出抛出异常语句的文件名和行号信息。这是因为标准C++异常是语言内置的,而其它类库的异常你不能控制其构造函数。这是一个小小的遗憾,不过你可以通过异常说明知道它是哪一种异常,可能在程序的哪一些C++函数中抛出,这样你也能很快地找到错误之处。 有了完善的异常类层次,你可以在程序中干任何事,异常过滤机制和堆栈跟踪会忠实的记录任何错误,除了你在析构函数抛出异常以及重复删除不为NULL的指针,这两种情况下程序还是会当掉,而且不能记录堆栈信息。当然,对于缓冲区溢出、堆写越界等情况,本类库还是无能为力,不过,你可以借助Purify、BoundsChecker等工具来检查程序运行中是否有这类问题。 5. 小结 谁说C++就不能有堆栈跟踪的功能。有了这个具有堆栈跟踪功能的异常类库,你将如虎添翼,必将加快你开发程序的进程。 Enjoy!
参考资料: 1. Everett N.McKay Mike Woodring.《WINDOWS程序调试》译者:何健辉等 中国电力出版社 2. Jeffrey Richter. 《WINDOWS核心编程》译者:王建华等 机械工业出版社 3. Bjarne Stroustrup. 《The C++ Programming Language, Special Edition》高教出版社 4. Jim Hyslop and Herb Sutter 《Redirections》 http://www.cuj.com/experts/1903/hyslop.htm