简记Windows结构化异常处理之二

=============================================================

标题:简记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_POINTERSEXCEPTION_RECORDCONTEXTEXCEPTION_POINTERS结构如下,它包含的两个指针成员,分别指向被压入栈的EXCEPTION_RECORDCONTEXT

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指令地址。NumberParametersExceptionInformation一起,用来进一步描述异常信息,通常不使用。

软件异常,在自己的程序中通过RaiseException函数抛出的异常。

void RaiseException(

  DWORD dwExceptionCode,

  DWORD dwExceptionFlags,

  DWORD nNumberOfArguments,

  const DWORD* lpArguments

);

很多时候,抛出异常比返回错误代码更好,可以避免调用者频繁判断调用成功与否并做清理,代码效率会更高,也更简洁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值