=============================================================
標題:簡記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_POINTERS,EXCEPTION_RECORD,CONTEXT。EXCEPTION_POINTERS結構如下,它包含的兩個指針成員,分別指向被壓入棧的EXCEPTION_RECORD和CONTEXT。
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指令地址。NumberParameters與ExceptionInformation一起,用來進一步描述異常信息,通常不使用。
軟件異常,在自己的程序中通過RaiseException函數拋出的異常。
void RaiseException(
DWORD dwExceptionCode,
DWORD dwExceptionFlags,
DWORD nNumberOfArguments,
const DWORD* lpArguments
);
很多時候,拋出異常比返回錯誤代碼更好,可以避免調用者頻繁判斷調用成功與否並做清理,代碼效率會更高,也更簡潔。