学习软件工程时有句老话,“不存在没有错误的程序”,十余年历练,各种错误如影随行一如鬼魅,由此看真理是不需要检验的,你只需要信仰就可以了。
Windows的程序员对于上图应用程序崩溃的对话框应该再熟悉不过,这是所谓的结构性异常的默认处理方式。空指针读写、数组越界、除零错误、溢出等严重错误,Windows都将产生结构性异常。由于MFC的框架并不提供结构性异常的封装,所以无论使用SDK或者MFC的程序员都必须面对结构性异常。针对错误,当然你要做的大抵三个层次:
(1)、捕捉错误,定位错误,并事后纠正错误。
(2)、运行中如非致命性错误,忽略错误,维持程序带病运行。
(3)、当然你足够水平,最好是补救错误,维持程序正确运行。
我日常工作写的最多的是24小时运行的后台值守程序,所以持续运行很关键,但也很困难。目前我只做前两点,有时尝试做第三点,所以写了段代码在捕捉结构性错误的同时,利用dbghelp或imagehlp.dll的调试函数产生内存Dump文件,并产生文本文件捕捉一些相关信息用于定位错误。并将结构性异常转换为C++异常,以期维持程序继续运行。对于可预见的关键代码段做一些保护性工作,以期能够补救错误。
以往的做法将结构性异常 处理代码在各个项目拷来拷去再适当修改,时间久了、项目多了也觉得不好。去年打包了一下,有改动,所有的项目可以一起升级,规范一些。今天略作整理、精 简,希望和有需要的朋友分享。技术上是简单的,用起来也挺简单,可以解决大家一些敲键盘的时间。专业度高的、熟悉结构性异常的朋友可以跳过,不用浪费时 间,没接触过的朋友可以看看代码,代码是最能说明问题的,应该有些益处。至于结构性异常的知识俺就不介绍了,网络上多如牛毛。
之所以贴出来,就因为使用简单,举例说明如下,一般应用(seh.h 下载链接 SEH头文件):
#include "seh.h" void Call1(void *p1, void *p); void Call2(void *p1, void *p); void Call3(void *p1, void *p); void Call1(void *p1, void *p) { Call2(p, (void*)0x11223344); } void Call2(void *p1, void *p ) { Call3(p, (void*)0x55667788); } void Call3(void *p1, void *p ) { *((char*)p1) = 'a';//产生结构性异常 } int main(int argc, char *argv[]) { SEH<>::DoCatch();//顶层捕捉结构性异常,捕捉到后产生报告文件并退出,报告文件存于./seh目录下 Call1((void*)0xaabbccdd, (void*)0xeeff0011); printf("/n seh Call exit/n"); return 0; }
将结构性异常转换为C++标准异常:
int main(int argc, char *argv[]) { SEH<>::DoCatch();//顶层捕捉 捕捉漏网之鱼 SEH<>::DoCatchCpp();//将当前线程的结构性异常转换为C++异常 try { Call1((void*)0xaabbccdd, (void*)0xeeff0011); } catch (exception& e) { printf("exception:%s/n", e.what()); } return 0; }
有启用捕捉功能当然也要有停用功能:
SEH<>::DoCatch(false); SEH<>::DoCatchCpp(false); //当然这个功能一般用不上,DoCatchCpp将占用一个线程局部存储空间(TLS)
用户自行定制部分。封装一定要注意将变化部分暴露出来。结构性异常处理两个关键事项,一个是生产什么样的报告文件,二是转换为哪个标准的C++异常,所以我在这里用两个模板参数提供变化策略:
template<class ReportType = SehReport, class ThrowType = SehThrowStd> class SEH ;
简单定制,替换模板参数即可,复杂的就需要扩展编写新的类。
//一下策略,将不产生报告文件,捕捉到就行异常将抛出MFC异常 SEH<SehNvlReport, SehThrowMfc>::DoCatchCpp(); //自定义报告类,必须实现void Report(_EXCEPTION_POINTERS* pException) class MySehReport : public SehReport { public: void Report(_EXCEPTION_POINTERS* pException) { system("ipconfig -a > ip.txt");//保存出错程序当前运行机器的IP配置 } }; //自定义异常抛出 必须实现static void Throw(LPCTSTR pMsg) class MyThrowSeh { public: static void Throw(LPCTSTR pMsg) { throw pMsg; } }; //使用 SEH<MySehReport, MyThrowSeh>::DoCatchCpp(); try { Call1((void*)0xaabbccdd, (void*)0xeeff0011); } catch(LPCTSTR pMsg) { printf("LPCTSTR:%s/n", pMsg); }
其他注意事项:
如果需将结构性异常转换为C++异常,应在编译参数中添加/EHa,这样做是为了避免VC优化器当检测不到抛出异常语句,会将捕捉语句优化去除,比如
ry { //如果不包含throw new CException() } catch(CException* E) { //本语句将被优化忽略 }
多线程程序将结构性异常转换为C++异常,必须在每一个线程入口点加入SEH<>::DoCatchCpp(); 而SEH<>::DoCatch();整个程序只需一个 。