Visual C++异常处理机制原理与应用(五)——C/C++结构化异常处理之try-except异常处理的使用(下)

在前面几篇文章中,我们介绍了VC++结构化异常处理的相关内容,目前其用途仅限于捕获代码中本来就存在的异常,比如访问违规、除数为0等异常情况。其实在很多场合,我们也完全可以利用这种机制,触发一个异常,然后让执行流程转入异常处理阶段。

一个最典型的例子是Windows中用于创建堆和分配堆内存的HeapCreate和HeapAlloc等函数,如果在其传入的dwFlags中加入了HEAP_GENERATE_EXCEPTIONS标记,则当分配或创建失败后,这些函数不再如其他WinAPI一样通过返回值体现,而是会直接抛出异常。这时,调用它们的代码也不必每调用一个API就检查返回值,而是在try块中写正常调用的执行流程,而在except块中写异常处理的代码。这样省略了之前每个WinAPI后面的if判断是否成功的语句,而将其集中到except中,使得程序的正常执行流程变得更加简单。这种由我们自己抛出的异常,称为“软件异常”。

在Win32API中,使用RaiseException函数抛出一个异常。其原型如下:

void WINAPI RaiseException(
__in DWORD dwExceptionCode,
__in DWORD dwExceptionFlags,
__in DWORD nNumberOfArguments,
__in const ULONG_PTR *lpArguments );

其中,

  • 第一个参数ExceptionCode,在C/C++结构化异常处理中可以使用GetExceptionCode()内联函数获取,也可以通过GetExceptionInformation获得ExceptionPointers结构,在通过其中的ExceptionRecord成员中的ExceptionCode字段得到。需要说明的是,这两个内联函数只能在异常处理中的过滤函数部分进行调用,而不能在其他场合使用。因此如果需要在其他地方用到,则必须在过滤函数中对其进行拷贝,以后使用这份拷贝的副本。(因为VC的异常处理机制将其构建在了栈中)
  • 第二参数ExceptionFlags,指定了异常是否能继续执行,如果传入0则表示可以继续执行出错处的指令;如果传入的是EXCEPTION_NONCONTINUABLE,则表示对应的异常处理过程中,允许用户选择重新执行那条指令。
  • 第三、四个参数分别用于传入附加信息的个数和数组首地址,其传入的附加参数个数不能超过15个。

如果了解Windows系统Ring3层结构化异常处理机制,就很容易发现这些传入的参数都是异常处理函数第一个参数——EXCEPTION_RECORD结构的成员,该结构定义如下:

typedef struct _EXCEPTION_RECORD {
  DWORD                    ExceptionCode;
  DWORD                    ExceptionFlags;
  struct _EXCEPTION_RECORD  *ExceptionRecord;
  PVOID                    ExceptionAddress;
  DWORD                    NumberParameters;
  ULONG_PTR                ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD, *PEXCEPTION_RECORD;

其中前两个和最后两个参数就是这里RaiseException传入的参数,而中间的两个,简要介绍一下:

ExceptionRecord字段用于嵌套异常处理,即在处理某个异常的过程中又发生了新的异常,则系统首先返回一个新异常对应的EXCEPTION_RECORD结构,并在其中的ExceptionRecord字段中指向旧的异常。因此,当嵌套异常发生时,该字段将形成一条链。

ExceptionAddress字段即为引发异常的语句所在的地址。

其实现在我们已经在一步一步地由C/C++的结构化异常处理走向Windows系统Ring3级的结构化异常处理,而C/C++的结构化异常处理利用了Windows系统Ring3级的结构化异常处理机制。为了更好地分析于C/C++的结构化异常处理的机制和原理。在后续文章中,我们将对Windows系统Ring3级的结构化异常处理进行一个比较全面地介绍。

你可能感兴趣的:(Visual C++异常处理机制原理与应用(五)——C/C++结构化异常处理之try-except异常处理的使用(下))