学习软件工程时有句老话,“不存在没有错误的程序”,十余年历练,各种错误如影随行一如鬼魅,由此看真理是不需要检验的,你只需要信仰就可以了。
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优化器当检测不到抛出异常语句,会将捕捉语句优化去除,比如
try
{
//如果不包含throw new CException()
}
catch(CException* E)
{
//本语句将被优化忽略
}
多线程程序将结构性异常转换为C++异常,必须在每一个线程入口点加入SEH<>::DoCatchCpp();
而SEH<>::DoCatch();整个程序只需一个 。