首页 > 代码库 > SEH异常机制杂记

SEH异常机制杂记

最近因为工作需要,简单的学习了一下SEH的异常处理机制和使用方法。

小结如下:

一、什么是SEH?

SEH("Structured Exception Handling"),即结构化异常处理,是微软在Windows系统中引入的异常处理机制。与C++的try…catch…类似,但是更强大更全面一些。

 

 

二、为什么要使用SEH?

因为使用了不可靠的代码,可能会出现指针越界、访问了非法指针等问题。而这些错误try…catch…是无法捕获的,这时我们就需要使用SEH来处理这些异常。

使用SEH还可以获得对进程更完整的控制,这一点在下面会讲到。

同时,SEH引入了finally的特性,使得有些情况下我们使用起来会很方便。

三、如何使用SEH

SEH有和C++相似的关键字:__try、__finally、__except、__leave。

SEH一种典型的用法如下:

__try{    //opreations}__finally{    //opreations}

在上面的语句中,不管__try语句块中有没有抛出异常,必然会执行__finally语句块中的内容。如果在__try中有return语句,会先将返回值保存起来,等__finally语句执行结束后再返回。例如:

__try{    return 
a
;}__finally{    return 
b
;}

这段代码在__try最后先将返回的a缓存起来,然后执行__finally,在__finally中,a又被b覆盖,最后返回b。

我们可以在__finally中释放动态申请的资源,以确保不会因为方法的异常退出而造成内存泄漏等情况。

然而,虽然 __finally 可以在绝大多数情况下保证被执行到,但当我们调用 ExitThread或ExitProcess时,线程或者进程将立即结束,因此__finally块中的内容是得不到执行的。同样的,在调用TerminateThread或者TerminateProcess后,__finally也是无法被执行到的。这就是为什么在我们编写程序时,要尽量避免调用这些函数原因之一。

总结,__finally块被执行的流程时,无外乎三种情况。第一种就是顺序执行到__finally块区域内的代码,这种情况很简单,容易理解;第二种就是goto语句或return语句引发的程序控制流离开当前__try块作用域时,系统自动完成对__finally块代码的调用;第三种就是由于在__try块中出现异常时,导致程序控制流离开当前__try块作用域,这种情况下也是由系统自动完成对__finally块的调用。无论是第 2种,还是第3种情况,毫无疑问,它们都会引起很大的系统开销,编译器在编译此类程序代码时,它会为这两种情况准备很多的额外代码。一般第2种情况,被称为“局部展开(LocalUnwinding)”;第3种情况,被称为“全局展开(GlobalUnwinding)”。

第三种情况,也就是由于出现异常导致的全局展开,作为程序猿,这也是无法避免的。毕竟你在利用SEH机制提高程序健壮性的同时,也不可避免带来一些额外的性能开销。

但是对于第二种情况,我们完全可以规避它,避免由局部展开带来的不必要的开销。实际这也是与结构化程序设计思想相一致的,也即一个程序模块应该只有一个入口和一个出口,程序模块内尽量避免使用goto语句等。最好的情况是我们不要在__try中使用这种语句,我们可以使用 __leave。

__leave的作用是直接跳到__try语句块的结尾,因此,我们可以在第一种情况下进入__finally块。

在SEH中,__try语句块除了可以和 __finally 搭配使用外,还可以和 __except 搭配使用,但 __finally和__except不可以同时存在。

__except有点类似与catch,但是又有所不同。catch通常是捕获一个对象,而__except则比较像一个函数,接受一个参数,且仅限于以下三个值:

标识符数值含义
EXCEPTION_EXECUTE_HANDLER1__except语句需要被执行,执行以后,跳转到__except块的后一条指令开始继续执行
EXCEPTION_CONTINUE_SEARCH0不执行__except语句的内容,继续向上抛出
EXCEPTION_CONTINUE_EXECUTION-1在__except语句执行完毕以后,跳转到异常发生的地方继续执行

第一种与catch类似,第二种相当于没有__except,第三种一般用于可以修复的异常,修复完成后让程序继续正常执行。

实际运用中一般不直接给__except上述的三个值,而是通过给__except一个表达式(通常是一个函数),使用GetExceptionCode()和GetExceptionInformation()两个函数获取异常的信息来判断传入什么值。

如何抛出异常

C++中,我们可以通过throw来给try…catch…抛出一个异常,而SEH也可以使用RaiseException来引发一个异常。

VOID RaiseException(    DWORD dwExceptionCode,    DWORD dwExceptionFlags,    DWORD nNumberOfArguments,    CONST ULONG_PTR* pArguments);

第一个参数dwExceptionCode为异常代码,使用windows定义的异常代码,也可以自己遵循标准的windows错误代码格式来定义一个异常代码。

第二个参数 dwExceptionFlags,必须是0或者 EXCEPTION_NONCONTINUABLE,表示使用EXCEPTION_CONTINUE_EXCEPTION来影响这个异常是否合法。

后两个参数表示传入给异常的参数数量及具体参数列表。我们可以通过 GetExceptionInformation来获取这些异常信息。

PS:

try…catch…与SEH不能在一个函数中同时使用。

__try语句块中也不能直接包涵将要析构的对象,但是可以通过包含一个函数,在函数中析构对象来解决。

SEH异常机制杂记