从本篇文章开始,将全面阐述__try,__except,__finally,__leave异常模型机制,它也便是Windows系列操做系统平台上提供的SEH模型。将在这里与你们分享SEH( 结构化异常处理)的学习过程和经验总结。 深刻理解请参阅<<windows 核心编程>>第23, 24章.程序员
string stdformat(const char *fmt, ...) { std::string strResult = ""; if (NULL != fmt) { va_list marker = NULL; va_start(marker, fmt); //初始化变量参数 size_t nLength = _vscprintf(fmt, marker) + 1; //获取格式化字符串长度 std::vector<char> vBuffer(nLength, '\0'); //建立用于存储格式化字符串的字符数组 int nWritten = _vsnprintf_s(&vBuffer[0], vBuffer.size(), nLength, fmt, marker); if (nWritten > 0) { strResult = &vBuffer[0]; } va_end(marker); //重置变量参数 } return strResult; } void Write2log(const char *pbuf) { string sout = pbuf; FILE *fpread = 0; fpread = fopen("c:\\wktt.log", "a+"); if (fpread) { fwrite(sout.c_str(), sout.length(), 1, fpread); fflush(fpread); fclose(fpread); fpread = 0; } } //异常日志 int SEH_filter(const char *ptr, DWORD code, struct _EXCEPTION_POINTERS *ep) { DWORD ExAddress = (DWORD)(ep->ExceptionRecord->ExceptionAddress); HMODULE hModule = GetModuleHandle(0); DWORD BaseAddress = (DWORD)hModule; DWORD off = 0; if (ExAddress > BaseAddress) off = ExAddress - BaseAddress; string sout = stdformat("comm.dll:%s [错误码:0x%X 偏移:0x%X eip:0x%X|esp:0x%X|参数num:%d] \r\n",ptr,code,off, ep->ContextRecord->Eip, ep->ContextRecord->Esp, ep->ExceptionRecord->NumberParameters); Write2log(sout.c_str()); return EXCEPTION_EXECUTE_HANDLER; } PVOID hhChatView(PVOID pArg) { __try { } __except (SEH_filter("hhChatView", GetExceptionCode(), GetExceptionInformation())) { } return 0; }
SEH实际包含两个主要功能:结束处理(termination handling)和异常处理(exception handling)编程
每当你创建一个try块,它必须跟随一个finally块或一个except块。windows
一个try 块以后不能既有finally块又有except块。但能够在try - except块中嵌套try - finally块,反过来 也能够。数组
__try __finally关键字用来标出结束处理程序两段代码的轮廓数据结构
无论保护体(try块) 是如何退出的。不论你在保护体中使用return,仍是goto,或者是longjump,结束处理程序 (finally块)都将被调用。ide
在try使用__leave关键字会引发跳转到try块的结尾函数
SEH有两项很是强大的功能。固然,首先是异常处理模型了,所以,这篇文章首先深刻阐述SEH提供的异常处理模型。另外,SEH还有一个特别强大的功能,这将在下一篇文章中进行详细介绍。学习
try-except入门 SEH的异常处理模型主要由try-except语句来完成,它与标准C++所定义的异常处理模型很是相似,也都是能够定义出受监控的代码模块,以及定义异常处理模块等。仍是老办法,看一个例子先,代码以下: //seh-test.cspa
void main() { // 定义受监控的代码模块 __try { puts("in try"); } //定义异常处理模块 __except(1) { puts("in except"); } } 操作系统
呵呵!是否是很简单,并且与C++异常处理模型很类似。固然,为了与C++异常处理模型相区别,VC编译器对关键字作了少量变更。首先是在每一个关键字加上两个下划线做为前缀,这样既保持了语义上的一致性,另外也尽最大可能来避免了关键字的有可能形成名字冲突而引发的麻烦等;其次,C++异常处理模型是使用catch关键字来定义异常处理模块,而SEH是采用__except关键字来定义。而且,catch关键字后面每每好像接受一个函数参数同样,能够是各类类型的异常数据对象;可是__except关键字则不一样,它后面跟的倒是一个表达式(能够是各类类型的表达式,后面会进一步分析)。
try-except进阶 与C++异常处理模型很类似,在一个函数中,能够有多个try-except语句。它们能够是一个平面的线性结构,也能够是分层的嵌套结构。例程代码以下:
// 例程1 // 平面的线性结构
void main() { __try { puts("in try"); } __except(1) { puts("in except"); } // 又一个try-except语句 __try { puts("in try1"); } __except(1) { puts("in except1"); } }
// 例程2 // 分层的嵌套结构
void main() { __try { puts("in try"); // 又一个try-except语句 __try { puts("in try1"); } __except(1) { puts("in except1"); } } __except(1) { puts("in except"); } }
// 例程3 // 分层的嵌套在__except模块中
void main() { __try { puts("in try"); } __except(1) { // 又一个try-except语句 __try { puts("in try1"); } __except(1) { puts("in except1"); } puts("in except"); } }
1. 受监控的代码模块被执行(也即__try定义的模块代码); 2. 若是上面的代码执行过程当中,没有出现异常的话,那么控制流将转入到__except子句以后的代码模块中; 3. 不然,若是出现异常的话,那么控制流将进入到__except后面的表达式中,也即首先计算这个表达式的值,以后再根据这个值,来决定作出相应的处理。这个值有三种状况,以下: EXCEPTION_CONTINUE_EXECUTION (–1) 异常被忽略,控制流将在异常出现的点以后,继续恢复运行。 EXCEPTION_CONTINUE_SEARCH (0) 异常不被识别,也即当前的这个__except模块不是这个异常错误所对应的正确的异常处理模块。系统将继续到上一层的try-except域中继续查找一个恰当的__except模块。 EXCEPTION_EXECUTE_HANDLER (1) 异常已经被识别,也即当前的这个异常错误,系统已经找到了并可以确认,这个__except模块就是正确的异常处理模块。控制流将进入到__except模块中。 try-except深刻 上面的内容中已经对try-except进行了全面的了解,可是有一点尚未阐述到。那就是如何在__except模块中得到异常错误的相关信息,这很是关键,它其实是进行异常错误处理的前提,也是对异常进行分层分级别处理的前提。可想而知,若是没有这些起码的信息,异常处理如何进行?所以获取异常信息很是的关键。Windows提供了两个API函数,以下:
LPEXCEPTION_POINTERS GetExceptionInformation(VOID); DWORD GetExceptionCode(VOID);
其中GetExceptionCode()返回错误代码,而GetExceptionInformation()返回更全面的信息,看它函数的声明,返回了一个LPEXCEPTION_POINTERS类型的指针变量。那么EXCEPTION_POINTERS结构如何呢?以下,
typedef struct _EXCEPTION_POINTERS { // exp PEXCEPTION_RECORD ExceptionRecord; PCONTEXT ContextRecord; } EXCEPTION_POINTERS;
呵呵!仔细瞅瞅,这是否是和上一篇文章中,用户程序所注册的异常处理的回调函数的两个参数类型同样。是的,的确没错!其中EXCEPTION_RECORD类型,它记录了一些与异常相关的信息;而CONTEXT数据结构体中记录了异常发生时,线程当时的上下文环境,主要包括寄存器的值。所以有了这些信息,__except模块即可以对异常错误进行很好的分类和恢复处理。不过特别须要注意的是,这两个函数只能是在__except后面的括号中的表达式做用域内有效,不然结果可能没有保证(至于为何,在后面深刻分析异常模型的实现时候,再作详细阐述)。看一个例程吧!代码以下:
int exception_access_violation_filter(LPEXCEPTION_POINTERS p_exinfo) { if(p_exinfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) { printf("存储保护异常\n"); return 1; } else return 0; } int exception_int_divide_by_zero_filter(LPEXCEPTION_POINTERS p_exinfo) { if(p_exinfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO) { printf("被0除异常\n"); return 1; } else return 0; } void main() { __try { __try { int* p; // 下面将致使一个异常 p = 0; *p = 45; } // 注意,__except模块捕获一个存储保护异常 __except(exception_access_violation_filter(GetExceptionInformation())) { puts("内层的except块中"); } //能够在此写除0异常的语句 int b = 0; int a = 1 / b; } // 注意,__except模块捕获一个被0除异常 __except(exception_int_divide_by_zero_filter(GetExceptionInformation())) { puts("外层的except块中"); } }
上面的程序运行结果以下:
存储保护异常 内层的except块中 Press any key to continue
呵呵!感受不错,你们能够在上面的程序基础之上改动一下,让它抛出一个被0除异常,看程序的运行结果是否是如预期那样。 最后还有一点须要阐述,在C++的异常处理模型中,有一个throw关键字,也即在受监控的代码中抛出一个异常,那么在SEH异常处理模型中,是否是也应该有这样一个相似的关键字或函数呢?是的,没错!SEH异常处理模型中,对异常划分为两大类,第一种就是上面一些例程中所见到的,这类异常是系统异常,也被称为硬件异常;还有一类,就是程序中本身抛出异常,被称为软件异常。怎么抛出呢?仍是Windows提供了的API函数,它的声明以下:
VOID RaiseException( DWORD dwExceptionCode, // exception code DWORD dwExceptionFlags, // continuable exception flag DWORD nNumberOfArguments, // number of arguments in array CONST DWORD *lpArguments // address of array of arguments );
很简单吧!实际上,在C++的异常处理模型中的throw关键字,最终也是对RaiseException()函数的调用,也便是说,throw是RaiseException的上层封装的更高级一类的函数,这之后再详细分析它的代码实现。这里仍是看一个简单例子吧!代码以下:
int seh_filer(int code) { switch(code) { case EXCEPTION_ACCESS_VIOLATION : printf("存储保护异常,错误代码:%x\n", code); break; case EXCEPTION_DATATYPE_MISALIGNMENT : printf("数据类型未对齐异常,错误代码:%x\n", code); break; case EXCEPTION_BREAKPOINT : printf("中断异常,错误代码:%x\n", code); break; case EXCEPTION_SINGLE_STEP : printf("单步中断异常,错误代码:%x\n", code); break; case EXCEPTION_ARRAY_BOUNDS_EXCEEDED : printf("数组越界异常,错误代码:%x\n", code); break; case EXCEPTION_FLT_DENORMAL_OPERAND : case EXCEPTION_FLT_DIVIDE_BY_ZERO : case EXCEPTION_FLT_INEXACT_RESULT : case EXCEPTION_FLT_INVALID_OPERATION : case EXCEPTION_FLT_OVERFLOW : case EXCEPTION_FLT_STACK_CHECK : case EXCEPTION_FLT_UNDERFLOW : printf("浮点数计算异常,错误代码:%x\n", code); break; case EXCEPTION_INT_DIVIDE_BY_ZERO : printf("被0除异常,错误代码:%x\n", code); break; case EXCEPTION_INT_OVERFLOW : printf("数据溢出异常,错误代码:%x\n", code); break; case EXCEPTION_IN_PAGE_ERROR : printf("页错误异常,错误代码:%x\n", code); break; case EXCEPTION_ILLEGAL_INSTRUCTION : printf("非法指令异常,错误代码:%x\n", code); break; case EXCEPTION_STACK_OVERFLOW : printf("堆栈溢出异常,错误代码:%x\n", code); break; case EXCEPTION_INVALID_HANDLE : printf("无效句病异常,错误代码:%x\n", code); break; default : if(code & (1<<29)) printf("用户自定义的软件异常,错误代码:%x\n", code); else printf("其它异常,错误代码:%x\n", code); break; } return 1; } void main() { __try { puts("try块中"); // 注意,主动抛出一个软异常 RaiseException(0xE0000001, 0, 0, 0); } __except(seh_filer(GetExceptionCode())) { puts("except块中"); } }
上面的程序运行结果以下: hello try块中 用户自定义的软件异常,错误代码:e0000001 except块中 world Press any key to continue
上面的程序很简单,这里不作进一步的分析。咱们须要重点讨论的是,在__except模块中如何识别不一样的异常,以便对异常进行很好的分类处理。毫无疑问,它固然是经过GetExceptionCode()或GetExceptionInformation ()函数来获取当前的异常错误代码,实际也便是DwExceptionCode字段。异常错误代码在winError.h文件中定义,它遵循Windows系统下统一的错误代码的规则。每一个DWORD被划分几个字段,以下表所示: 例如咱们能够在winbase.h文件中找到EXCEPTION_ACCESS_VIOLATION的值为0 xC0000005,将这个异常代码值拆开,来分析看看它的各个bit位字段的涵义。 C 0 0 0 0 0 0 5 (十六进制) 1100 0000 0000 0000 0000 0000 0000 0101 (二进制) 第3 0位和第3 1位都是1,表示该异常是一个严重的错误,线程可能不可以继续往下运行,必需要及时处理恢复这个异常。第2 9位是0,表示系统中已经定义了异常代码。第2 8位是0,留待后用。第1 6 位至2 7位是0,表示是FACILITY_NULL设备类型,它表明存取异常可发生在系统中任何地方,不是使用特定设备才发生的异常。第0位到第1 5位的值为5,表示异常错误的代码。 若是程序员在程序代码中,计划抛出一些自定义类型的异常,必需要规划设计好本身的异常类型的划分,按照上面的规则来填充异常代码的各个字段值,如上面示例程序中抛出一个异常代码为0xE0000001软件异常。
总结 (1) C++异常模型用try-catch语法定义,而SEH异常模型则用try-except语法; (2) 与C++异常模型类似,try-except也支持多层的try-except嵌套。 (3) 与C++异常模型不一样的是,try-except模型中,一个try块只能是有一个except块;而C++异常模型中,一个try块能够有多个catch块。 (4) 与C++异常模型类似,try-except模型中,查找搜索异常模块的规则也是逐级向上进行的。可是稍有区别的是,C++异常模型是按照异常对象的类型来进行匹配查找的;而try-except模型则不一样,它经过一个表达式的值来进行判断。若是表达式的值为1(EXCEPTION_EXECUTE_HANDLER),表示找到了异常处理模块;若是值为0(EXCEPTION_CONTINUE_SEARCH),表示继续向上一层的try-except域中继续查找其它可能匹配的异常处理模块;若是值为-1(EXCEPTION_CONTINUE_EXECUTION),表示忽略这个异常,注意这个值通常不多用,由于它很容易致使程序难以预测的结果,例如,死循环,甚至致使程序的崩溃等。 (5) __except关键字后面跟的表达式,它能够是各类类型的表达式,例如,它能够是一个函数调用,或是一个条件表达式,或是一个逗号表达式,或干脆就是一个整型常量等等。最经常使用的是一个函数表达式,而且经过利用GetExceptionCode()或GetExceptionInformation ()函数来获取当前的异常错误信息,便于程序员有效控制异常错误的分类处理。 (6) SEH异常处理模型中,异常被划分为两大类:系统异常和软件异常。其中软件异常经过RaiseException()函数抛出。