從本篇文章開始,將全面闡述__try,__except,__finally,__leave異常模型機制,它也便是Windows系列操做系統平臺上提供的SEH模型。將在這裏與你們分享SEH( 結構化異常處理)的學習過程和經驗總結。 深刻理解請參閱<<windows 核心編程>>第23, 24章.程序員
string stdformat(const char *fmt, ...) { std::string strResult = ""; if (NULL != fmt) { va_list marker = NULL; va_start(marker, fmt); //初始化變量參數 size_t nLength = _vscprintf(fmt, marker) + 1; //獲取格式化字符串長度 std::vector<char> vBuffer(nLength, '\0'); //建立用於存儲格式化字符串的字符數組 int nWritten = _vsnprintf_s(&vBuffer[0], vBuffer.size(), nLength, fmt, marker); if (nWritten > 0) { strResult = &vBuffer[0]; } va_end(marker); //重置變量參數 } return strResult; } void Write2log(const char *pbuf) { string sout = pbuf; FILE *fpread = 0; fpread = fopen("c:\\wktt.log", "a+"); if (fpread) { fwrite(sout.c_str(), sout.length(), 1, fpread); fflush(fpread); fclose(fpread); fpread = 0; } } //異常日誌 int SEH_filter(const char *ptr, DWORD code, struct _EXCEPTION_POINTERS *ep) { DWORD ExAddress = (DWORD)(ep->ExceptionRecord->ExceptionAddress); HMODULE hModule = GetModuleHandle(0); DWORD BaseAddress = (DWORD)hModule; DWORD off = 0; if (ExAddress > BaseAddress) off = ExAddress - BaseAddress; string sout = stdformat("comm.dll:%s [錯誤碼:0x%X 偏移:0x%X eip:0x%X|esp:0x%X|參數num:%d] \r\n",ptr,code,off, ep->ContextRecord->Eip, ep->ContextRecord->Esp, ep->ExceptionRecord->NumberParameters); Write2log(sout.c_str()); return EXCEPTION_EXECUTE_HANDLER; } PVOID hhChatView(PVOID pArg) { __try { } __except (SEH_filter("hhChatView", GetExceptionCode(), GetExceptionInformation())) { } return 0; }
SEH實際包含兩個主要功能:結束處理(termination handling)和異常處理(exception handling)編程
每當你創建一個try塊,它必須跟隨一個finally塊或一個except塊。windows
一個try 塊以後不能既有finally塊又有except塊。但能夠在try - except塊中嵌套try - finally塊,反過來 也能夠。數組
__try __finally關鍵字用來標出結束處理程序兩段代碼的輪廓數據結構
無論保護體(try塊) 是如何退出的。不論你在保護體中使用return,仍是goto,或者是longjump,結束處理程序 (finally塊)都將被調用。ide
在try使用__leave關鍵字會引發跳轉到try塊的結尾函數
SEH有兩項很是強大的功能。固然,首先是異常處理模型了,所以,這篇文章首先深刻闡述SEH提供的異常處理模型。另外,SEH還有一個特別強大的功能,這將在下一篇文章中進行詳細介紹。學習
try-except入門 SEH的異常處理模型主要由try-except語句來完成,它與標準C++所定義的異常處理模型很是相似,也都是能夠定義出受監控的代碼模塊,以及定義異常處理模塊等。仍是老辦法,看一個例子先,代碼以下: //seh-test.cspa
void main() { // 定義受監控的代碼模塊 __try { puts("in try"); } //定義異常處理模塊 __except(1) { puts("in except"); } } 操作系統
呵呵!是否是很簡單,並且與C++異常處理模型很類似。固然,爲了與C++異常處理模型相區別,VC編譯器對關鍵字作了少量變更。首先是在每一個關鍵字加上兩個下劃線做爲前綴,這樣既保持了語義上的一致性,另外也盡最大可能來避免了關鍵字的有可能形成名字衝突而引發的麻煩等;其次,C++異常處理模型是使用catch關鍵字來定義異常處理模塊,而SEH是採用__except關鍵字來定義。而且,catch關鍵字後面每每好像接受一個函數參數同樣,能夠是各類類型的異常數據對象;可是__except關鍵字則不一樣,它後面跟的倒是一個表達式(能夠是各類類型的表達式,後面會進一步分析)。
try-except進階 與C++異常處理模型很類似,在一個函數中,能夠有多個try-except語句。它們能夠是一個平面的線性結構,也能夠是分層的嵌套結構。例程代碼以下:
// 例程1 // 平面的線性結構
void main() { __try { puts("in try"); } __except(1) { puts("in except"); } // 又一個try-except語句 __try { puts("in try1"); } __except(1) { puts("in except1"); } }
// 例程2 // 分層的嵌套結構
void main() { __try { puts("in try"); // 又一個try-except語句 __try { puts("in try1"); } __except(1) { puts("in except1"); } } __except(1) { puts("in except"); } }
// 例程3 // 分層的嵌套在__except模塊中
void main() { __try { puts("in try"); } __except(1) { // 又一個try-except語句 __try { puts("in try1"); } __except(1) { puts("in except1"); } puts("in except"); } }
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後面的括號中的表達式做用域內有效,不然結果可能沒有保證(至於爲何,在後面深刻分析異常模型的實現時候,再作詳細闡述)。看一個例程吧!代碼以下:
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塊中"); } }
上面的程序運行結果以下:
存儲保護異常 內層的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的上層封裝的更高級一類的函數,這之後再詳細分析它的代碼實現。這裏仍是看一個簡單例子吧!代碼以下:
int seh_filer(int code) { switch(code) { case EXCEPTION_ACCESS_VIOLATION : printf("存儲保護異常,錯誤代碼:%x\n", code); break; case EXCEPTION_DATATYPE_MISALIGNMENT : printf("數據類型未對齊異常,錯誤代碼:%x\n", code); break; case EXCEPTION_BREAKPOINT : printf("中斷異常,錯誤代碼:%x\n", code); break; case EXCEPTION_SINGLE_STEP : printf("單步中斷異常,錯誤代碼:%x\n", code); break; case EXCEPTION_ARRAY_BOUNDS_EXCEEDED : printf("數組越界異常,錯誤代碼:%x\n", 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("浮點數計算異常,錯誤代碼:%x\n", code); break; case EXCEPTION_INT_DIVIDE_BY_ZERO : printf("被0除異常,錯誤代碼:%x\n", code); break; case EXCEPTION_INT_OVERFLOW : printf("數據溢出異常,錯誤代碼:%x\n", code); break; case EXCEPTION_IN_PAGE_ERROR : printf("頁錯誤異常,錯誤代碼:%x\n", code); break; case EXCEPTION_ILLEGAL_INSTRUCTION : printf("非法指令異常,錯誤代碼:%x\n", code); break; case EXCEPTION_STACK_OVERFLOW : printf("堆棧溢出異常,錯誤代碼:%x\n", code); break; case EXCEPTION_INVALID_HANDLE : printf("無效句病異常,錯誤代碼:%x\n", code); break; default : if(code & (1<<29)) printf("用戶自定義的軟件異常,錯誤代碼:%x\n", code); else printf("其它異常,錯誤代碼:%x\n", code); break; } return 1; } void main() { __try { puts("try塊中"); // 注意,主動拋出一個軟異常 RaiseException(0xE0000001, 0, 0, 0); } __except(seh_filer(GetExceptionCode())) { puts("except塊中"); } }
上面的程序運行結果以下: 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()函數拋出。