=============================================================
标题:简记Windows结构化异常处理之二
备注:参考《Windows核心编程》
日期:2011.3.28
姓名:朱铭雷
=============================================================
异常处理程序的语法结构:
__try
{
//
}
__except(// 异常过滤)
{
// 异常处理
}
示例1:
#include <iostream>
#include <excpt.h>
using namespace std;
int Div(int i)
{
int n = 10;
__try
{
n = n / i;
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
n = 10;
}
return n;
}
int _tmain(int argc, _TCHAR* argv[])
{
int i = 2, ii = 0;
int ir = Div(i);
int iir = Div(ii);
cout << ir << endl;
cout << iir << endl;
system("PAUSE");
return 0;
}
Div(i):Div函数正常执行,n / i的结果为5,然后将n的值返回。
Div(ii):n / 0会产生硬件异常,控制流转到异常过滤,异常过滤表达式的值是EXCEPTION_EXECUTE_HANDLER,则将执行异常处理程序,n=10,最后返回n的值。
执行结果:
系统异常处理流程图(摘自《Windows核心编程》):
这个图中非常重要的一步是“全局展开”的过程,以及三个异常过滤标识符的导向。
经验法则:使用异常处理程序,只处理那些我们知道怎么处理的异常。
示例2:
PBYTE RobustMemDup(PBYTE pbSrc, size_t cb)
{
PBYTE pbDup = NULL;
__try
{
pbDup = (PBYTE)malloc(cb);
memcpy(pbDup, pbSrc, cb);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
free(pbDup);
pbDup = NULL;
}
return pbDup;
}
如果pbSrc参数传入非法地址或者malloc函数失败,会导致memcpy抛出访问违规异常,导致异常过滤程序执行,过滤程序又将程序控制流交给except块。在except块里,释放内存并将puDup至为NULL。如果调用者给函数传入合法地址,并且malloc函数调用成功,则函数正常执行,实现内存复制,并将新分配的内存块地址返回给调用者。
全局展开导致所有已经开始执行但尚未完成的try_finally块得以继续执行。
全局展开流程图:
全局展开示例:
void Fun1()
{
// 1. Do any processing here.
...
__try
{
// 2. Call another function.
Fun2();
// Code here never executes.
}
__except(/* 6. Evaluate filter.*/EXCEPTION_EXECUTE_HANDLER)
{
// 8. After the unwind, the exception handler executes.
MessageBox(...);
}
// 9. Exception handled--continue execution.
}
void Fun2()
{
DWORD dwTemp = 0;
// 3. Do any processing here.
...
__try
{
// 4. Request permission to access protected data.
WaitForSingleObject(g_hSem, INFINITE);
// 5. Modify the data, an exception is generated here.
g_dwProtectedData = 5 / dwTemp;
}
__finally
{
// 7. Global unwind occurs because filter evaluated to EXCEPTION_EXECUTE_HANDLER.
// Allow others to use protected data.
ReleaseSemaphore(g_hSem, 1, NULL);
}
// Continue processing--never executes.
...
}
通过流程图和这个示例,可以理解全局展开的执行顺序。
finally块中的return语句将阻止全局展开,示例:
void Fun1()
{
__try
{
Fun2(); // 1.
}
__except(/* 4. */EXCEPTION_EXECUTE_HANDLER)
{
MessageBeep(0);
}
MessageBox(...); // 7.
}
void Fun2()
{
Fun3(); // 2.
MessageBox(...); // 6.
}
void Fun3()
{
__try
{
strcpy(NULL, NULL); // 3.
}
__finally
{
return; // 5.
}
}
数字标号标示执行顺序。
上面几个示例程序中,异常过滤程序均简单的指定了一个常量,如EXCEPTION_EXECUTION_HANDLER,实际上,可以让异常过滤程序调用一个函数,在该函数中根据实际情况来返回三个标识符中的其中一个。
经验法则:“在异常过滤程序中,试图纠正错误,然后返回EXCEPTION_CONTINUE_EXECUTION,让程序继续执行”,这一方法要谨慎使用。
GetExceptionCode
DWORD GetExceptionCode(void);
该函数返回一个标识(异常代码),以表明刚刚发生了什么类型的异常。如EXCEPTION_INT_DIVIDE_BY_ZERO,表示程序试图做整数除0运算。该函数只能在异常过滤程序或者异常处理程序中使用,而且不能在异常滤过程序所调用的函数中使用。
异常代码遵循Windows错误代码规则
GetExceptionInformation
异常发生时,操作系统向线程栈压入三个数据结构,EXCEPTION_POINTERS,EXCEPTION_RECORD,CONTEXT。EXCEPTION_POINTERS结构如下,它包含的两个指针成员,分别指向被压入栈的EXCEPTION_RECORD和CONTEXT。
typedef struct _EXCEPTION_POINTERS {
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT ContextRecord;
} EXCEPTION_POINTERS;
CONTEXT包含的信息和CPU有关,EXCEPTION_RECORD结构中包含与CPU无关的异常信息。
typedef struct _EXCEPTION_RECORD {
DWORD ExceptionCode;
DWORD ExceptionFlags;
struct _EXCEPTION_RECORD* ExceptionRecord;
PVOID ExceptionAddress;
DWORD NumberParameters;
DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;
通过GetExceptionInformation函数能够获取指向该EXCEPTION_POINTERS得指针。不过该函数只能在异常过滤程序中调用,因为上面三个结构仅在这时才有效。不过我们当然可以将EXCEPTION_RECORD结构保存起来备用。
LPEXCEPTION_POINTERS GetExceptionInformation(void);
EXCEPTION_RECORD结构:ExceptionCode是异常代码,同GetExceptionCode返回值。ExceptionFlags,如果是0,标示可以继续的异常,如果是EXCEPTION_NONCONTINUABLE,标示不能继续的异常。ExceptionRecord指向刚刚发生的另一个未处理异常的EXCEPTION_RECORD结构。ExceptionAddress是导致异常的cpu指令地址。NumberParameters与ExceptionInformation一起,用来进一步描述异常信息,通常不使用。
软件异常,在自己的程序中通过RaiseException函数抛出的异常。
void RaiseException(
DWORD dwExceptionCode,
DWORD dwExceptionFlags,
DWORD nNumberOfArguments,
const DWORD* lpArguments
);
很多时候,抛出异常比返回错误代码更好,可以避免调用者频繁判断调用成功与否并做清理,代码效率会更高,也更简洁。