C與C++中的異常處理17 (轉)

gugu99發表於2008-08-06
C與C++中的異常處理17 (轉)[@more@]

:namespace prefix = o ns = "urn:schemas--com::office" />

  我在Part2介紹了Structured Exception Handling(簡稱SEH)。在那時我就說過,SEH是window及其平臺上的專有的。它不是定義在ISO C++標準中的,使用它的將不能跨編譯器移植。因為我注重於標準相容和可移植性,所以我對將專有的SEH對映為ISO標準C++的exception handing(簡稱EH)很感興趣。

  同時,我不是SEH的專家。對它的瞭解絕大部分來自於本專欄前面的研究。當我考慮混合使用SEH與EH時,我猜想解決方法應該是困難的和不是顯而易見的。這是它花了我兩個星期的原因:我預料到需要額外的時間來研究和試驗。

  很高興,我完全錯了。我不知道的是Visual C++執行期庫直接支援了絕大部分我所想要的東西。不用創造新的方法了,我可以展示你Visual C++已經支援了的東西,以及改造為所需要的東西的方法。基於這個目的,我將研究同一個例子的四個不同版本。

 

1.1  Version 1:定義一個轉換

  捆綁SEH和EH的方法分兩步:

l  一個自定義的轉換函式來捕獲SEH的異常並將它對映為C++的異常。

l  一個Visual C++執行期庫函式來這個轉換函式

  使用者自定義的轉換函式必要有如下形式:

void my_translator(unsigned code, EXCEPTION_POINTERS *info);

  轉換函式接受一個SEH異常(透過給定的異常code和info來定義的)。然後丟擲一個C++異常,以此將傳入的SEH異常對映為向外傳的C++異常。這個C++異常將出現在原來的SEH異常發生點上並向外傳播。

  這個機制非常象std::set_tenate()和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;

  }

 

  執行它的話,這個控制檯程式將導致如此一個ssagebox:

  它是由於一個未被捕獲的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_DIV_BY_ZERO:

  // ...

  case EXCEPTION_STACK_OVERFLOW:

  // ...

  // ...

  }

  這樣的查詢需要windows.h中的資訊,以知道最初的SEH異常碼的含意。這樣的需求違背了structured_exception的抽象原則。此外,switch語句也經常違背了多型的原則。從使用者程式碼的角度看,你通常應該用繼承和模板來實現它。

  C++標準執行庫在這方面提供了一些指導。如我在Part3中勾畫的,標頭檔案定義了一個異常類層次,std::exception是根結點。這個根類定義了虛成員what(),它返回一個編譯器自定義的NTBS(C++標準中是“以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  總結束

(略)

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-1008592/,如需轉載,請註明出處,否則將追究法律責任。

相關文章