快捷搜索:

您的位置:澳门新葡4473网站 > 热门贴子 > 【 转】__try,__except,__finally,__leave异常模型机

【 转】__try,__except,__finally,__leave异常模型机

发布时间:2019-10-11 01:30编辑:热门贴子浏览(98)

    近期一直被一个问题所困扰,就是写出来的程序老是出现无故崩溃,有的地方自己知道可能有问题,但是有的地方又根本没办法知道有什么问题。更苦逼的事情是,我们的程序是需要7x24服务客户,虽然不需要实时精准零差错,但是总不能出现断线丢失数据状态。故刚好通过处理该问题,找到了一些解决方案,怎么捕获访问非法内存地址或者0除以一个数。从而就遇到了这个结构化异常处理,今就简单做个介绍认识下,方便大家遇到相关问题后,首先知道问题原因,再就是如何解决。废话不多说,下面进入正题。

    转自:

    什么是结构化异常处理

    结构化异常处理(structured exception handling,下文简称:SEH),是作为一种系统机制引入到操作系统中的,本身与语言无关。在我们自己的程序中使用SEH可以让我们集中精力开发关键功能,而把程序中所可能出现的异常进行统一的处理,使程序显得更加简洁且增加可读性。

    使用SHE,并不意味着可以完全忽略代码中可能出现的错误,但是我们可以将软件工作流程和软件异常情况处理进行分开,先集中精力干重要且紧急的活,再来处理这个可能会遇到各种的错误的重要不紧急的问题(不紧急,但绝对重要)

    当在程序中使用SEH时,就变成编译器相关的。其所造成的负担主要由编译程序来承担,例如编译程序会产生一些表(table)来支持SEH的数据结构,还会提供回调函数。

    注:
    不要混淆SHE和C++ 异常处理。C++ 异常处理再形式上表现为使用关键字catchthrow,这个SHE的形式不一样,再windows Visual C++中,是通过编译器和操作系统的SHE进行实现的。

    在所有 Win32 操作系统提供的机制中,使用最广泛的未公开的机制恐怕就要数SHE了。一提到SHE,可能就会令人想起 *__try__finally* 和 *__except* 之类的词儿。SHE实际上包含两方面的功能:终止处理(termination handing)异常处理(exception handing)

    导读: 
    从本篇文章开始,将全面阐述__try,__except,__finally,__leave异常模型机制,它也即是Windows系列操作系统平台上提供的SEH模型。主人公阿愚将在这里与大家分享SEH( 结构化异常处理)的学习过程和经验总结。 深入理解请参阅<<windows 核心编程>>第23, 24章.

    终止处理

    终止处理程序确保不管一个代码块(被保护代码)是如何退出的,另外一个代码块(终止处理程序)总是能被调用和执行,其语法如下:

    __try
    {
        //Guarded body
        //...
    }
    __finally
    {
        //Terimnation handler
        //...
    }
    

    **__try __finally** 关键字标记了终止处理程序的两个部分。操作系统和编译器的协同工作保障了不管保护代码部分是如何退出的(无论是正常退出、还是异常退出)终止程序都会被调用,即**__finally**代码块都能执行。

    SEH实际包含两个主要功能:结束处理(termination handling)和异常处理(exception handling) 

    try块的正常退出与非正常退出

    try块可能会因为returngoto,异常等非自然退出,也可能会因为成功执行而自然退出。但不论try块是如何退出的,finally块的内容都会被执行。

    int Func1()
    {
        cout << __FUNCTION__ << endl;
        int nTemp = 0;
        __try{
            //正常执行
            nTemp = 22;
            cout << "nTemp = " << nTemp << endl;
        }
        __finally{
            //结束处理
            cout << "finally nTemp = " << nTemp << endl;
        }
        return nTemp;
    }
    
    int Func2()
    {
        cout << __FUNCTION__ << endl;
        int nTemp = 0;
        __try{
            //非正常执行
            return 0;
            nTemp = 22;
            cout << "nTemp = " << nTemp << endl;
        }
        __finally{
            //结束处理
            cout << "finally nTemp = " << nTemp << endl;
        }
        return nTemp;
    }
    

    结果如下:

    Func1
    nTemp = 22  //正常执行赋值
    finally nTemp = 22  //结束处理块执行
    
    Func2
    finally nTemp = 0   //结束处理块执行
    

    以上实例可以看出,通过使用终止处理程序可以防止过早执行return语句,当return语句视图退出try块的时候,编译器会让finally代码块再它之前执行。对于在多线程编程中通过信号量访问变量时,出现异常情况,能顺利是否信号量,这样线程就不会一直占用一个信号量。当finally代码块执行完后,函数就返回了。

    为了让整个机制运行起来,编译器必须生成一些额外代码,而系统也必须执行一些额外工作,所以应该在写代码的时候避免再try代码块中使用return语句,因为对应用程序性能有影响,对于简单demo问题不大,对于要长时间不间断运行的程序还是悠着点好,下文会提到一个关键字**__leave**关键字,它可以帮助我们发现有局部展开开销的代码。

    一条好的经验法则:不要再终止处理程序中包含让try块提前退出的语句,这意味着从try块和finally块中移除return,continue,break,goto等语句,把这些语句放在终止处理程序以外。这样做的好处就是不用去捕获哪些try块中的提前退出,从而时编译器生成的代码量最小,提高程序的运行效率和代码可读性。

    每当你建立一个try块,它必须跟随一个finally块或一个except块。

    ####finally块的清理功能及对程序结构的影响

    在编码的过程中需要加入需要检测,检测功能是否成功执行,若成功的话执行这个,不成功的话需要作一些额外的清理工作,例如释放内存,关闭句柄等。如果检测不是很多的话,倒没什么影响;但若又许多检测,且软件中的逻辑关系比较复杂时,往往需要化很大精力来实现繁琐的检测判断。结果就会使程序看起来结构比较复杂,大大降低程序的可读性,而且程序的体积也不断增大。

    对应这个问题我是深有体会,过去在写通过COM调用WordVBA的时候,需要层层获取对象、判断对象是否获取成功、执行相关操作、再释放对象,一个流程下来,本来一两行的VBA代码,C++ 写出来就要好几十行(这还得看操作的是几个什么对象)。

    下面就来一个方法让大家看看,为什么有些人喜欢脚本语言而不喜欢C++的原因吧。

    为了更有逻辑,更有层次地操作 OfficeMicrosoft 把应用(Application)按逻辑功能划分为如下的树形结构

    Application(WORD 为例,只列出一部分)
      Documents(所有的文档)
            Document(一个文档)
                ......
      Templates(所有模板)
            Template(一个模板)
                ......
      Windows(所有窗口)
            Window
            Selection
            View
            .....
      Selection(编辑对象)
            Font
            Style
            Range
            ......
      ......
    

    只有了解了逻辑层次,我们才能正确的操纵 Office。举例来讲,如果给出一个VBA语句是:

    Application.ActiveDocument.SaveAs "c:abc.doc"
    

    那么,我们就知道了,这个操作的过程是:

    1. 第一步,取得Application
    2. 第二步,从Application中取得ActiveDocument
    3. 第三步,调用 Document 的函数 SaveAs,参数是一个字符串型的文件名。

    这只是一个最简单的的VBA代码了。来个稍微复杂点的如下,在选中处,插入一个书签:

     ActiveDocument.Bookmarks.Add Range:=Selection.Range, Name:="iceman"
    

    此处流程如下:

    1. 获取Application
    2. 获取ActiveDocument
    3. 获取Selection
    4. 获取Range
    5. 获取Bookmarks
    6. 调用方法Add

    获取每个对象的时候都需要判断,还需要给出错误处理,对象释放等。在此就给出伪码吧,全写出来篇幅有点长

    #define RELEASE_OBJ(obj) if(obj != NULL) 
                            obj->Realse();
    
    BOOL InsertBookmarInWord(const string& bookname)
    {
        BOOL ret = FALSE;
        IDispatch* pDispApplication = NULL;
        IDispatch* pDispDocument = NULL;
        IDispatch* pDispSelection = NULL;
        IDispatch* pDispRange = NULL;
        IDispatch* pDispBookmarks = NULL;
        HRESULT hr = S_FALSE;
    
        hr = GetApplcaiton(..., &pDispApplication);
        if (!(SUCCEEDED(hr) || pDispApplication == NULL))
            return FALSE;
    
        hr = GetActiveDocument(..., &pDispDocument);
        if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
            RELEASE_OBJ(pDispApplication);
            return FALSE;
        }
    
        hr = GetActiveDocument(..., &pDispDocument);
        if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
            RELEASE_OBJ(pDispApplication);
            return FALSE;
        }
    
        hr = GetSelection(..., &pDispSelection);
        if (!(SUCCEEDED(hr) || pDispSelection == NULL)){
            RELEASE_OBJ(pDispApplication);
            RELEASE_OBJ(pDispDocument);
            return FALSE;
        }
    
        hr = GetRange(..., &pDispRange);
        if (!(SUCCEEDED(hr) || pDispRange == NULL)){
            RELEASE_OBJ(pDispApplication);
            RELEASE_OBJ(pDispDocument);
            RELEASE_OBJ(pDispSelection);
            return FALSE;
        }
    
        hr = GetBookmarks(..., &pDispBookmarks);
        if (!(SUCCEEDED(hr) || pDispBookmarks == NULL)){
            RELEASE_OBJ(pDispApplication);
            RELEASE_OBJ(pDispDocument);
            RELEASE_OBJ(pDispSelection);
            RELEASE_OBJ(pDispRange);
            return FALSE;
        }
    
        hr = AddBookmark(...., bookname);
        if (!SUCCEEDED(hr)){
            RELEASE_OBJ(pDispApplication);
            RELEASE_OBJ(pDispDocument);
            RELEASE_OBJ(pDispSelection);
            RELEASE_OBJ(pDispRange);
            RELEASE_OBJ(pDispBookmarks);
            return FALSE;
        }
        ret = TRUE;
        return ret;
    

    这只是伪码,虽然也可以通过goto减少代码行,但是goto用得不好就出错了,下面程序中稍不留神就goto到不该取得地方了。

    BOOL InsertBookmarInWord2(const string& bookname)
    {
        BOOL ret = FALSE;
        IDispatch* pDispApplication = NULL;
        IDispatch* pDispDocument = NULL;
        IDispatch* pDispSelection = NULL;
        IDispatch* pDispRange = NULL;
        IDispatch* pDispBookmarks = NULL;
        HRESULT hr = S_FALSE;
    
        hr = GetApplcaiton(..., &pDispApplication);
        if (!(SUCCEEDED(hr) || pDispApplication == NULL))
            goto exit6;
    
        hr = GetActiveDocument(..., &pDispDocument);
        if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
            goto exit5;
        }
    
        hr = GetActiveDocument(..., &pDispDocument);
        if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
            goto exit4;
        }
    
        hr = GetSelection(..., &pDispSelection);
        if (!(SUCCEEDED(hr) || pDispSelection == NULL)){
            goto exit4;
        }
    
        hr = GetRange(..., &pDispRange);
        if (!(SUCCEEDED(hr) || pDispRange == NULL)){
            goto exit3;
        }
    
        hr = GetBookmarks(..., &pDispBookmarks);
        if (!(SUCCEEDED(hr) || pDispBookmarks == NULL)){
            got exit2;
        }
    
        hr = AddBookmark(...., bookname);
        if (!SUCCEEDED(hr)){
            goto exit1;
        }
    
        ret = TRUE;
    exit1:
        RELEASE_OBJ(pDispApplication);
    exit2:
        RELEASE_OBJ(pDispDocument);
    exit3:
        RELEASE_OBJ(pDispSelection);
    exit4:
        RELEASE_OBJ(pDispRange);
    exit5:
        RELEASE_OBJ(pDispBookmarks);
    exit6:
        return ret;
    

    此处还是通过SEH的终止处理程序来重新该方法,这样是不是更清晰明了。

    BOOL InsertBookmarInWord3(const string& bookname)
    {
        BOOL ret = FALSE;
        IDispatch* pDispApplication = NULL;
        IDispatch* pDispDocument = NULL;
        IDispatch* pDispSelection = NULL;
        IDispatch* pDispRange = NULL;
        IDispatch* pDispBookmarks = NULL;
        HRESULT hr = S_FALSE;
    
        __try{
            hr = GetApplcaiton(..., &pDispApplication);
            if (!(SUCCEEDED(hr) || pDispApplication == NULL))
                return FALSE;
    
            hr = GetActiveDocument(..., &pDispDocument);
            if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
                return FALSE;
            }
    
            hr = GetActiveDocument(..., &pDispDocument);
            if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
                return FALSE;
            }
    
            hr = GetSelection(..., &pDispSelection);
            if (!(SUCCEEDED(hr) || pDispSelection == NULL)){
                return FALSE;
            }
    
            hr = GetRange(..., &pDispRange);
            if (!(SUCCEEDED(hr) || pDispRange == NULL)){
                return FALSE;
            }
    
            hr = GetBookmarks(..., &pDispBookmarks);
            if (!(SUCCEEDED(hr) || pDispBookmarks == NULL)){
                return FALSE;
            }
    
            hr = AddBookmark(...., bookname);
            if (!SUCCEEDED(hr)){
                return FALSE;
            }
    
            ret = TRUE;
        }
        __finally{
            RELEASE_OBJ(pDispApplication);
            RELEASE_OBJ(pDispDocument);
            RELEASE_OBJ(pDispSelection);
            RELEASE_OBJ(pDispRange);
            RELEASE_OBJ(pDispBookmarks);
        }
        return ret;
    

    这几个函数的功能是一样的。可以看到在InsertBookmarInWord中的清理函数(RELEASE_OBJ)到处都是,而InsertBookmarInWord3中的清理函数则全部集中在finally块,如果在阅读代码时只需看try块的内容即可了解程序流程。这两个函数本身都很小,可以细细体会下这两个函数的区别。

    一个try 块之后不能既有finally块又有except块。但可以在try - except块中嵌套try - finally块,反过来
    也可以。

    关键字 __leave

    try块中使用**__leave关键字会使程序跳转到try块的结尾,从而自然的进入finally块。
    对于上例中的InsertBookmarInWord3try块中的return完全可以用
    __leave** 来替换。两者的区别是用return会引起try过早退出系统会进行局部展开而增加系统开销,若使用**__leave**就会自然退出try块,开销就小的多。

    BOOL InsertBookmarInWord4(const string& bookname)
    {
        BOOL ret = FALSE;
        IDispatch* pDispApplication = NULL;
        IDispatch* pDispDocument = NULL;
        IDispatch* pDispSelection = NULL;
        IDispatch* pDispRange = NULL;
        IDispatch* pDispBookmarks = NULL;
        HRESULT hr = S_FALSE;
    
        __try{
            hr = GetApplcaiton(..., &pDispApplication);
            if (!(SUCCEEDED(hr) || pDispApplication == NULL))
                __leave;
    
            hr = GetActiveDocument(..., &pDispDocument);
            if (!(SUCCEEDED(hr) || pDispDocument == NULL))
                __leave;
    
            hr = GetActiveDocument(..., &pDispDocument);
            if (!(SUCCEEDED(hr) || pDispDocument == NULL))
                __leave;
    
            hr = GetSelection(..., &pDispSelection);
            if (!(SUCCEEDED(hr) || pDispSelection == NULL))
                __leave;
    
            hr = GetRange(..., &pDispRange);
            if (!(SUCCEEDED(hr) || pDispRange == NULL))
                __leave;
    
            hr = GetBookmarks(..., &pDispBookmarks);
            if (!(SUCCEEDED(hr) || pDispBookmarks == NULL))
                __leave;
    
            hr = AddBookmark(...., bookname);
            if (!SUCCEEDED(hr))
                __leave;
    
            ret = TRUE;
        }
        __finally{
            RELEASE_OBJ(pDispApplication);
            RELEASE_OBJ(pDispDocument);
            RELEASE_OBJ(pDispSelection);
            RELEASE_OBJ(pDispRange);
            RELEASE_OBJ(pDispBookmarks);
        }
        return ret;
    }
    

    __try  __finally关键字用来标出结束处理程序两段代码的轮廓

    异常处理程序

    软件异常是我们都不愿意看到的,但是错误还是时常有,比如CPU捕获类似非法内存访问和除0这样的问题,一旦侦查到这种错误,就抛出相关异常,操作系统会给我们应用程序一个查看异常类型的机会,并且运行程序自己处理这个异常。异常处理程序结构代码如下

      __try {
          // Guarded body
        }
        __except ( exception filter ) {
          // exception handler
        }
    

    注意关键字**__except**,任何try块,后面必须更一个finally代码块或者except代码块,但是try后又不能同时有finallyexcept块,也不能同时有多个finnalyexcept块,但是可以相互嵌套使用

    不管保护体(try块)
    是如何退出的。不论你在保护体中使用return,还是goto,或者是longjump,结束处理程序
    (finally块)都将被调用。

    异常处理基本流程

    int Func3()
    {
        cout << __FUNCTION__ << endl;
        int nTemp = 0;
        __try{
            nTemp = 22;
            cout << "nTemp = " << nTemp << endl;
        }
        __except (EXCEPTION_EXECUTE_HANDLER){
            cout << "except nTemp = " << nTemp << endl;
        }
        return nTemp;
    }
    
    int Func4()
    {
        cout << __FUNCTION__ << endl;
        int nTemp = 0;
        __try{
            nTemp = 22/nTemp;
            cout << "nTemp = " << nTemp << endl;
        }
        __except (EXCEPTION_EXECUTE_HANDLER){
            cout << "except nTemp = " << nTemp << endl;
        }
        return nTemp;
    }
    

    结果如下:

    Func3
    nTemp = 22  //正常执行
    
    Func4
    except nTemp = 0 //捕获异常,
    

    Func3try块只是一个简单操作,故不会导致异常,所以except块中代码不会被执行,Func4try块视图用22除0,导致CPU捕获这个事件,并抛出,系统定位到except块,对该异常进行处理,该处有个异常过滤表达式,系统中有三该定义(定义在Windows的Excpt.h中):

    1. EXCEPTION_EXECUTE_HANDLER:
        我知道这个异常了,我已经写了代码来处理它,让这些代码执行吧,程序跳转到except块中执行并退出
    2. EXCEPTION_CONTINUE_SERCH
        继续上层搜索处理except代码块,并调用对应的异常过滤程序
    3. EXCEPTION_CONTINUE_EXECUTION
        返回到出现异常的地方重新执行那条CPU指令本身
    

    面是两种基本的使用方法:

    • 方式一:直接使用过滤器的三个返回值之一
    __try {
       ……
    }
    __except ( EXCEPTION_EXECUTE_HANDLER ) {
       ……
    }
    
    • 方式二:自定义过滤器
    __try {
       ……
    }
    __except ( MyFilter( GetExceptionCode() ) )
    {
       ……
    }
    
    LONG MyFilter ( DWORD dwExceptionCode )
    {
      if ( dwExceptionCode == EXCEPTION_ACCESS_VIOLATION )
        return EXCEPTION_EXECUTE_HANDLER ;
      else
        return EXCEPTION_CONTINUE_SEARCH ;
    }
    

    在try使用__leave关键字会引起跳转到try块的结尾

    .NET4.0中捕获SEH异常

    在.NET 4.0之后,CLR将会区别出一些异常(都是SEH异常),将这些异常标识为破坏性异常(Corrupted State Exception)。针对这些异常,CLR的catch块不会捕捉这些异常,一下代码也没有办法捕捉到这些异常。

    try{
        //....
    }
    catch(Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
    

    因为并不是所有人都需要捕获这个异常,如果你的程序是在4.0下面编译并运行,而你又想在.NET程序里捕捉到SEH异常的话,有两个方案可以尝试:

    • 在托管程序的.config文件里,启用legacyCorruptedStateExceptionsPolicy这个属性,即简化的.config文件类似下面的文件:
    App.Config
    
    <?xml version="1.0"?>
    <configuration>
     <startup>
       <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
     </startup>
        <runtime>
          <legacyCorruptedStateExceptionsPolicy enabled="true" />
        </runtime>
    </configuration>
    

    这个设置告诉CLR 4.0,整个.NET程序都要使用老的异常捕捉机制。

    • 在需要捕捉破坏性异常的函数外面加一个HandleProcessCorruptedStateExceptions属性,这个属性只控制一个函数,对托管程序的其他函数没有影响,例如:
    [HandleProcessCorruptedStateExceptions]
    try{
        //....
    }
    catch(Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
    

     SEH有两项非常强大的功能。当然,首先是异常处理模型了,因此,这篇文章首先深入阐述SEH提供的异常处理模型。另外,SEH还有一个特别强大的功能,这将在下一篇文章中进行详细介绍。

    try-except入门
      SEH的异常处理模型主要由try-except语句来完成,它与标准C++所定义的异常处理模型非常类似,也都是可以定义出受监控的代码模块,以及定义异常处理模块等。还是老办法,看一个例子先,代码如下: 
    //seh-test.c

    图片 1

    void main()
    {
        // 定义受监控的代码模块
        __try
        {
            puts("in try");
        }
        //定义异常处理模块
        __except(1)
        {
            puts("in except");
        }
    }
    

    图片 2

     呵呵!是不是很简单,而且与C++异常处理模型很相似。当然,为了与C++异常处理模型相区别,VC编译器对关键字做了少许变动。首先是在每个关键字加上两个下划线作为前缀,这样既保持了语义上的一致性,另外也尽最大可能来避免了关键字的有可能造成名字冲突而引起的麻烦等;其次,C++异常处理模型是使用catch关键字来定义异常处理模块,而SEH是采用__except关键字来定义。并且,catch关键字后面往往好像接受一个函数参数一样,可以是各种类型的异常数据对象;但是__except关键字则不同,它后面跟的却是一个表达式(可以是各种类型的表达式,后面会进一步分析)。

    try-except进阶
      与C++异常处理模型很相似,在一个函数中,可以有多个try-except语句。它们可以是一个平面的线性结构,也可以是分层的嵌套结构。例程代码如下:

    // 例程1
    // 平面的线性结构

    图片 3

    void main()
    {
        __try
        {
            puts("in try");
        }
        __except(1)
        {
            puts("in except");
        }
    
    
        // 又一个try-except语句
        __try
        {
            puts("in try1");
        }
        __except(1)
        {
            puts("in except1");
        }
    }
    

    图片 4

    // 例程2
    // 分层的嵌套结构

    图片 5

    void main()
    {
        __try
        {
            puts("in try");
            // 又一个try-except语句
            __try
            {
                puts("in try1");
            }
            __except(1)
            {
                puts("in except1");
            }
        }
        __except(1)
        {
            puts("in except");
        }
    }
    

    图片 6

    // 例程3
    // 分层的嵌套在__except模块中

    图片 7

    void main()
    {
        __try
        {
            puts("in try");
        }
        __except(1)
        {
            // 又一个try-except语句
            __try
            {
                puts("in try1");
            }
            __except(1)
            {
                puts("in except1");
            }
    
            puts("in except");
        }
    }
    

    图片 8

     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后面的括号中的表达式作用域内有效,否则结果可能没有保证(至于为什么,在后面深入分析异常模型的实现时候,再做详细阐述)。看一个例程吧!代码如下:

    图片 9

    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块中");
        }
    }
    

    图片 10

    上面的程序运行结果如下:

    存储保护异常
    内层的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的上层封装的更高级一类的函数,这以后再详细分析它的代码实现。这里还是看一个简单例子吧!代码如下:

    图片 11

    int seh_filer(int code)
    {
        switch(code)
        {
        case EXCEPTION_ACCESS_VIOLATION :
            printf("存储保护异常,错误代码:%xn", code);
            break;
        case EXCEPTION_DATATYPE_MISALIGNMENT :
            printf("数据类型未对齐异常,错误代码:%xn", code);
            break;
        case EXCEPTION_BREAKPOINT :
            printf("中断异常,错误代码:%xn", code);
            break;
        case EXCEPTION_SINGLE_STEP :
            printf("单步中断异常,错误代码:%xn", code);
            break;
        case EXCEPTION_ARRAY_BOUNDS_EXCEEDED :
            printf("数组越界异常,错误代码:%xn", 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("浮点数计算异常,错误代码:%xn", code);
            break;
        case EXCEPTION_INT_DIVIDE_BY_ZERO :
            printf("被0除异常,错误代码:%xn", code);
            break;
        case EXCEPTION_INT_OVERFLOW :
            printf("数据溢出异常,错误代码:%xn", code);
            break;
        case EXCEPTION_IN_PAGE_ERROR :
            printf("页错误异常,错误代码:%xn", code);
            break;
        case EXCEPTION_ILLEGAL_INSTRUCTION :
            printf("非法指令异常,错误代码:%xn", code);
            break;
        case EXCEPTION_STACK_OVERFLOW :
            printf("堆栈溢出异常,错误代码:%xn", code);
            break;
        case EXCEPTION_INVALID_HANDLE :
            printf("无效句病异常,错误代码:%xn", code);
            break;
        default :
            if(code & (1<<29))
                printf("用户自定义的软件异常,错误代码:%xn", code);
            else
                printf("其它异常,错误代码:%xn", code);
            break;
        }
    
        return 1;
    }
    
    
    void main()
    {
        __try
        {
            puts("try块中");
    
            // 注意,主动抛出一个软异常
            RaiseException(0xE0000001, 0, 0, 0);
        }
        __except(seh_filer(GetExceptionCode()))
        {
            puts("except块中");
        }
    
    }
    

    图片 12

    上面的程序运行结果如下:
    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()函数抛出。RaiseException()函数的作用类似于C++异常模型中的throw语句。

    C++不常用关键字(__leave)

    **总结__finally块被执行的流程时,无外乎三种情况。第一种就是顺序执行到__finally块区域内的代码,这种情况很简单,容易理解;第二种就是goto语句或return语句引发的程序控制流离开当前__try块作用域时,系统自动完成对__finally块代码的调用;第三种就是由于在__try块中出现异常时,导致程序控制流离开当前__try块作用域,这种情况下也是由系统自动完成对__finally块的调用。无论是第 2种,还是第3种情况,毫无疑问,它们都会引起很大的系统开销,编译器在编译此类程序代码时,它会为这两种情况准备很多的额外代码。一般第2种情况,被称为“局部展开(LocalUnwinding)”;第3种情况,被称为“全局展开(GlobalUnwinding)”。在后面阐述SEH实现的时候会详细分析到这一点。
    第3种情况,也即由于出现异常而导致的“全局展开”,对于程序员而言,这也许是无法避免的,因为你在利用异常处理机制提高程序可靠健壮性的同时,不可避免的会引起性能上其它的一些开销。呵呵!这世界其实也算瞒公平的,有得必有失。

      但是,对于第2种情况,程序员完全可以有效地避免它,避免“局部展开”引起的不必要的额外开销。实际这也是与结构化程序设计思想相一致的,也即一个程序模块应该只有一个入口和一个出口,程序模块内尽量避免使用goto语句等。但是,话虽如此,有时为了提高程序的可读性,程序员在编写代码时,有时可能不得不采用一些与结构化程序设计思想相悖的做法,例如,在一个函数中,可能有多处的return语句。针对这种情况,SEH提供了一种非常有效的折衷方案,那就是__leave关键字所起的作用,它既具有像goto语句和return语句那样类似的作用(由于检测到某个程序运行中的错误,需要马上离开当前的 __try块作用域),但是又避免了“局部展开” 的额外开销。还是看个例子吧!代码如下:** 

    图片 13

    #include <stdio.h>
    
    void test()
    {
    puts("hello");
    __try
    {
    int* p;
    puts("__try块中");
    
    // 直接跳出当前的__try作用域
    __leave;
    p = 0;
    *p = 25;
    }
    __finally
    {
    // 这里会被执行吗?当然
    puts("__finally块中");
    }
    
    puts("world");
    }
    
    void main()
    {
    __try
    {
    test();
    }
    __except(1)
    {
    puts("__except块中");
    }
    }
    

    图片 14

    上面的程序运行结果如下:
    hello
    __try块中
    __finally块中
    world
    Press any key to continue

    本文由澳门新葡4473网站发布于热门贴子,转载请注明出处:【 转】__try,__except,__finally,__leave异常模型机

    关键词: