C与C++中的异常处理17

1.     C++异常和Visual C++ SEH的混合使用

    我在Part2介绍了Structured Exception Handling(简称SEH)。在那时我就说过,SEHwindow及其平台上的编译器专有的。它不是定义在ISO C++标准中的,使用它的程序将不能跨编译器移植。因为我注重于标准兼容和可移植性,所以我对将windows专有的SEH映射为ISO标准C++exception handing(简称EH)很感兴趣。

    同时,我不是SEH的专家。对它的了解绝大部分来自于本专栏前面的研究。当我考虑混合使用SEHEH时,我猜想解决方法应该是困难的和不是显而易见的。这是它花了我两个星期的原因:我预料到需要额外的时间来研究和试验。

    很高兴,我完全错了。我不知道的是Visual C++运行期库直接支持了绝大部分我所想要的东西。不用创造新的方法了,我可以展示你Visual C++已经支持了的东西,以及改造为所需要的东西的方法。基于这个目的,我将研究同一个例子的四个不同版本。

 

1.1     Version 1:定义一个转换函数

    捆绑SEHEH的方法分两步:

l         一个用户自定义的转换函数来捕获SEH的异常并将它映射为C++的异常。

l         一个Visual C++运行期库函数来安装这个转换函数

    用户自定义的转换函数必要有如下形式:

void my_translator(unsigned code, EXCEPTION_POINTERS *info);

    转换函数接受一个SEH异常(通过给定的异常codeinfo来定义的)。然后抛出一个C++异常,以此将传入的SEH异常映射为向外传的C++异常。这个C++异常将出现在原来的SEH异常发生点上并向外传播。

    这个机制非常象std::set_terminate()std::set_unexpected()。要安装转换函数,要调用Visual C++库函数_set_se_translator()。这个函数申明在头文件eh.h中:

typedef void (*_se_translator_function)(unsigned, EXCEPTION_POINTERS *);

 

_se_translator_function _set_se_translator(_se_translator_function);

    它接受一个指向新转换函数的指针,返回上次安装的指针。一旦安装了一个转换函数,前一次的就丢失了;任何时候只有一个转换函数有效。(在多线程程序中,每个线程有一个独立的转换函数。)

    如果还没安装过转换函数,第一次调用_set_se_translator()返回值可能是(也可能不是)NULL。也就是说,不能不分青红皂白就通过其返回的指针调用函数。很有趣的,如果返回值是NULL,而你又通过此NULL调用函数,将产生一个SEH异常,并且进入你刚刚安装的转换函数。

    一个简单的例子:

#include

using namespace std;

 

int main()

   {

   try

      {

      *(int *) 0 = 0; // generate Structured Exception

      }

   catch (unsigned exception)

      {

      cout << "caught C++ exception " << hex << exception << endl;

      }

   return 0;

   }

 

    运行它的话,这个控制台程序将导致如此一个windows messagebox

    它是由于一个未被捕获的SEH异常传递到程序外面造成的。

    现在,增加一个异常转换函数,并将Visual C++运行库设为使用这个转换函数:

#include

using namespace std;

 

#include "windows.h"

 

static void my_translator(unsigned code, EXCEPTION_POINTERS *)

   {

   throw code;

   }

 

int main()

   {

   _set_se_translator(my_translator);

   try

      {

      *(int *) 0 = 0; // generate Structured Exception

      }

   catch (unsigned exception)

      {

      cout << "caught C++ exception " << hex << exception << endl;

      }

   return 0;

   }

    再运行程序。现在将看到:

caught C++ exception c0000005

    my_translator()截获了SEH异常,并转换为C++异常,其类型为unsigned,内容为SEH异常码(本例中为C0000005h,它是一个非法读取错误)。因为这个C++异常出现在原来的SEH异常发生点,也就说在try块中,所以被try块的异常处理函数捕获了。

 

1.2     Version 2:定义一个转换对象

    上面的例子非常简单,将每个SEH异常转换为一个unsigned值。实际上,你可能需要一个比较复杂的异常对象:

#include

using namespace std;

 

//#include "windows.h"

 

#include "structured_exception.h"

 

/*static void my_translator(unsigned code, EXCEPTION_POINTERS *)

   {

   throw code;

   }*/

 

int main()

   {

//_set_se_translator(my_translator);

   structured_exception::install();

   try

      {

      *(int *) 0 = 0; // generate Structured Exception

      }

   catch (structured_exception const &exception)

      {

      cout << "caught C++ exception " << hex << exception.what()

            << " thrown from " << exception.where() << endl;

      }

   return 0;

   }

 

    这个例子抛出了一个用户自定义类型(structured_exception)的C++异常。为了让这个例子更具实际意义,也更方便阅读,我将structured_exception的申明放到了头文件structured_exception.h中:

#if !defined INC_structured_exception_

    #define  INC_structured_exception_

 

#include "windows.h"

 

class structured_exception

   {

public:

   structured_exception(EXCEPTION_POINTERS const &) throw();

   static void install() throw();

   unsigned what() const throw();

   void const *where() const throw();

private:

   void const *address_;

   unsigned code_;

   };

 

#endif // !defined INC_structured_exception_

    其实现文件为:

#include "structured_exception.h"

 

#include "eh.h"

 

//

//  ::

//

static void my_translator(unsigned, EXCEPTION_POINTERS *info)

   {

   throw structured_exception(*info);

   }

 

//

//  structured_exception::

//

structured_exception::structured_exception

      (EXCEPTION_POINTERS const &info) throw()

   {

   EXCEPTION_RECORD const &exception = *(info.ExceptionRecord);

   address_ = exception.ExceptionAddress;

   code_ = exception.ExceptionCode;

   }

 

void structured_exception::install() throw()

   {

   _set_se_translator(my_translator);

   }

 

unsigned structured_exception::what() const throw()

   {

   return code_;

   }

 

void const *structured_exception::where() const throw()

   {

   return address_;

   }

 

    这些函数的意义是:

l         my_translator()是异常转换函数。我把它从main文件中移到这儿。于是,main文件不再需要包含windows.h了。

l         install()将运行器库的全局转换函数设置为my_translator()

l         structured_exception的构造函数接收并解析SEH异常的信息。

l         what()返回SEH异常的异常码。

l         where()返回SEH异常发生的地点。注意,where()的返回类型是void const *,虽然C++标准不同意将代码地址转换为void指针。我只是重复了Micorsoft的用法,因为Visual C++运行库将地址存在了SEH异常的EXCEPTION_RECORD的一个void *成员中了。

    编译并链接这三个文件。运行结果是:

caught C++ exception c0000005 thrown from 0040181D

(其中的代码地址值在你的系统上可能有所不同。)

 

1.3     Version 3:模仿C++标准运行库

    my_translator()中,所有的SEH异常映射为同样的structured_exception类型。这使得异常容易被捕获,因为它们匹配于我们的唯一的异常处理函数:

catch (structured_exception const &exception)

    虽然捕获了异常,但我们没有办法事先知道异常的类型。唯一能做的是运行期查询,调用这个异常的what()成员:

catch (structured_exception const &exception)

   {

   switch (exception.what())

      {

      case EXCEPTION_ACCESS_VIOLATION:

         // ...

      case EXCEPTION_INT_DIVIDE_BY_ZERO:

         // ...

      case EXCEPTION_STACK_OVERFLOW:

         // ...

      // ...

   }

    这样的查询需要windows.h中的信息,以知道最初的SEH异常码的含意。这样的需求违背了structured_exception的抽象原则。此外,switch语句也经常违背了多态的原则。从用户代码的角度看,你通常应该用继承和模板来实现它。

    C++标准运行库在这方面提供了一些指导。如我在Part3中勾画的,头文件定义了一个异常类层次,std::exception是根结点。这个根类定义了虚成员what(),它返回一个编译器自定义的NTBSC++标准中是“以NULL结束的字符串”)。每个继承类指定自己的what()的返回值。虽然C++标准没有规定这些值的内容,但我相信标准委员会打算用这个字符串来描述异常的类型或含意的。

    根据这种精神,standard_exception的申明是:

#if !defined INC_structured_exception_

    #define  INC_structured_exception_

 

#include "eh.h"

#include "windows.h"

 

class structured_exception

    {

public:

   structured_exception(EXCEPTION_POINTERS const &) throw();

   static void install() throw();

   virtual char const *what() const throw();

   void const *where() const throw();

private:

   void const *address_;

   //unsigned code_;

   };

 

class access_violation : public structured_exception

   {

public:

   access_violation(EXCEPTION_POINTERS const &) throw();

   virtual char const *what() const throw();

   };

 

class divide_by_zero : public structured_exception

   {

public:

   divide_by_zero(EXCEPTION_POINTERS const &) throw();

   virtual char const *what() const throw();

   };

 

#endif // !defined INC_structured_exception_

 

    实现是:

#include

using namespace std;

 

#include "structured_exception.h"

 

#include "windows.h"

 

//

//  ::

//

static void my_translator(unsigned code, EXCEPTION_POINTERS *info)

   {

   switch (code)

      {

      case EXCEPTION_ACCESS_VIOLATION:

         throw access_violation(*info);

         break;

      case EXCEPTION_INT_DIVIDE_BY_ZERO:

      case EXCEPTION_FLT_DIVIDE_BY_ZERO:

         throw divide_by_zero(*info);

         break;

      default:

         throw structured_exception(*info);

         break;

      }

   }

 

//

//  structured_exception::

//

structured_exception::structured_exception

      (EXCEPTION_POINTERS const &info) throw()

   {

   EXCEPTION_RECORD const &exception = *(info.ExceptionRecord);

   address_ = exception.ExceptionAddress;

   //code_ = exception.ExceptionCode;

   }

 

void structured_exception::install() throw()

   {

   _set_se_translator(my_translator);

   }

 

char const *structured_exception::what() const throw()

   {

   return "unspecified Structured Exception";

   }

 

void const *structured_exception::where() const throw()

   {

   return address_;

   }

 

//

//  access_violation::

//

access_violation::access_violation

      (EXCEPTION_POINTERS const &info) throw()

      : structured_exception(info)

   {

   }

 

char const *access_violation::what() const throw()

   {

   return "access violation";

   }

 

//

//  divide_by_zero::

//

divide_by_zero::divide_by_zero

      (EXCEPTION_POINTERS const &info) throw()

      : structured_exception(info)

   {

   }

 

char const *divide_by_zero::what() const throw()

   {

   return "divide by zero";

   }

 

注意:

l         那些本来在用户的异常处理函数中的switch语句,现在移到了my_translator()中。不再是将所有SEH异常映射为单个值(如version 1中)或单个类型的对象(version 2),现在的my_translator()将它们映射为多个类型的对象(取决于运行时的实际环境)。

l         structured_exception成为了一个基类。我没有让它成为纯虚类,这是跟从了C++标准运行库的引导(std::exception是个实体类)。

l         我没有定义任何析构函数,因为编译器隐含提供的的析构函数对这些简单类足够了。如果我定义了析构函数,它们将需要定义为virtual

l         what()现在返回了一个用户友好的文本,取代了原来的SEH异常码。

l         因为我不再测试和显示这些代码,我去掉了数据成员code_。这使得structured_exception对象的大小减小了。(别太高兴:节省的空间又被新增的vptr指针抵销了,因为有了虚函数。)

l         因为模板方式更好,你应该放弃这种继承模式的。我将它留给你作为习题。

    试一下新的方案,将main文件改为:

#include

using namespace std;

 

#include "structured_exception.h"

 

int main()

   {

   structured_exception::install();

   //

   //  discriminate exception by dynamic type

   //

   try

      {

      *(int *) 0 = 0; // generate Structured Exception

      }

   catch (structured_exception const &exception)

      {

      cout << "caught " << exception.what() << endl;

      }

   //

   //  discriminate exception by static type

   //

   try

      {

      static volatile int i = 0;

      i = 1 / i; // generate Structured Exception

      }

   catch (access_violation const &)

      {

      cout << "caught access violation" << endl;

      }

   catch (divide_by_zero const &)

      {

      cout << "caught divide by zero" << endl;

      }

   catch (structured_exception const &)

      {

      cout << "caught unspecified Structured Exception" << endl;

      }

   return 0;

   }

 

    再次运行,结果是:

caught access violation

caught divide by zero

 

1.4     Version 4:匹配于C++标准运行库

    我们所有的standard_exception继承类都提供公有的成员

virtual char const *what() const;

来识别异常的动态类型。我不是随便选取的函数名:所有的C++标准运行库中的std::exception继承类为同样的目的提供了同样的公有成员。并且,what()是每个继承类的唯一的多态函数。

    你可能已经注意到:

#include

 

class structured_exception : public std::exception

   {

public:

   structured_exception(EXCEPTION_POINTERS const &info) throw();

   static void install() throw();

   virtual char const *what() const throw();

   void const *where() const throw();

private:

   void const *address_;

   };

 

    因为structured_exception现在也是一个std:exception,我们可以用一个异常处理函数来同时捕获这个异常族:

catch (std::exception const &exception)

    并且用同样的多态函数来获取异常的类型:

catch (std::exception const &exception)

   {

   cout << "caught " << exception.what();

   }

    用这样的方案,SEH异常能够表现得与标准C++的固有行为一致。同时,我们仍然能够特殊对待structured_exceptions并访问它的特殊成员:

catch (structured_exception const &exception)

   {

   cout << "caught Structured Exception from " << exception.where();

   }

    当然,如果你想放弃没有出现在std::exception继承体系中的类成员,如where(),你完全可以不使用基类structured_exception,而是直接从std::exception继承出access_violation等类。例如:一个divide-by-zero异常表示了一个程序值域控制错误,也就是说是个逻辑错误。你所以想直接从std::logic_error甚至是std::out_of_range派生devide_by_zero类。

    我建议你看一下C++标准subclause 19.1 (“Exception classes”)以更好地理解C++标准运行库的异常继承体系,以及如何更好地将你的自定义异常熔入此继承体系。

 

1.5     总结束

(略)

你可能感兴趣的:(C与C++中的异常处理17)