明輝手游網(wǎng)中心:是一個免費提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺!

Visual C++ 中的結(jié)構(gòu)異常處理

[摘要]jimmy 戰(zhàn)志杰 編譯 本文編譯自Jeffrey Richter先生的“Advanced Windows”部分章節(jié)。 1、引言 在“C++中例外的處理”一文中(見計算機世界網(wǎng)2001年12月20日),我們討論了C++中的例外(或異常)處理。本文將進一步探討Visual C++中的結(jié)構(gòu)異常處理。 ...
jimmy 戰(zhàn)志杰 編譯

本文編譯自Jeffrey Richter先生的“Advanced Windows”部分章節(jié)。
1、引言
在“C++中例外的處理”一文中(見計算機世界網(wǎng)2001年12月20日),我們討論了C++中的例外(或異常)處理。本文將進一步探討Visual C++中的結(jié)構(gòu)異常處理。
想象一下,如果在編程過程中你不需要考慮任何錯誤,你的程序永遠不會出錯,有足夠的內(nèi)存,你需要的文件永遠存在,這將是一件多么愉快的事。這時你的程序不需要太多的if語句轉(zhuǎn)來轉(zhuǎn)去,非常容易寫,容易讀,也容易理解。如果你認為這樣的編程環(huán)境是一種夢想,那么你就會喜歡結(jié)構(gòu)異常處理(structu reed exception handling)。
結(jié)構(gòu)異常處理的本質(zhì)就是讓你專心于如何去完成你的任務(wù)。如果在程序運行過程中出現(xiàn)任何錯誤,系統(tǒng)會接收(catch)并通知(notify)你。雖然利用結(jié)構(gòu)異常處理你不可能完全忽略你的程序出錯的可能性,但是結(jié)構(gòu)異常處理確確實實允許你將你的主要任務(wù)與錯誤處理分離開來。這種分離使得你可以集中精力于你的工作,而在以后在考慮可能的錯誤。
結(jié)構(gòu)異常處理的主要工作是由編譯器來完成的,而不是由操作系統(tǒng)。編譯器在遇到例外程序段時需要產(chǎn)生額外的特殊代碼來支持結(jié)構(gòu)異常處理。所以,每一個編譯器產(chǎn)品供應(yīng)商可能使用自己的語法和規(guī)定。這里我們采用微軟的Visual C++編譯器來進行討論。
注意不要將這里討論的結(jié)構(gòu)異常處理與C++中的異常處理混為一談。C++中的異常處理是另一種形式的異常處理,它使用了C++的關(guān)鍵詞catch和throw。
微軟最早在Visual C++版本2.0引進結(jié)構(gòu)異常處理。結(jié)構(gòu)異常處理主要由兩部分組成:中斷處理(termination handling)和例外處理(exception handling)。
2、中斷處理句柄(termination handler)
2.1、中斷處理句柄定義
中斷處理句柄保證了,不論進程如何離開另一程序段--這里稱之為守衛(wèi)體(guarded body),該句柄內(nèi)的程序段永遠會被調(diào)用和執(zhí)行。微軟的Visual C++編譯器的中斷處理句柄語法為
__try {
// Guarded body
.
.
.
}
__finally {
// Termination handler
.
.
.
}
這里的__try和__finally勾畫出了中斷處理句柄的兩個部分。在上面的例子中,操作系統(tǒng)和編譯器一起保證了不論包含在__try內(nèi)的程序段出現(xiàn)何種情況,包含在__finally內(nèi)的程序段永遠會被運行。不論你在__try內(nèi)的程序段中調(diào)用return、goto或longjump,__finally內(nèi)的中斷處理句柄永遠會被調(diào)用。其流程為
// 1、執(zhí)行try程序段前的代碼
__try {
// 2、執(zhí)行try程序段內(nèi)的代碼
}
__finally {
// 3、執(zhí)行finally程序段內(nèi)的代碼
}
// 4、執(zhí)行finally程序段后的代碼
2.2、幾個例子
下面我們通過幾個具體例子來討論中斷處理句柄是如何工作的。
2.2.1、例1--Funcenstein1
清單一給出了我們的第一個例子。
DWORD Funcenstein1(void) {
DWORD dwTemp;
// 1. Do any processing here.
.
.
.
__try {
// 2. request permission to access protected data, and then use it.
WaitForSingleObject(g_hSem, INFINITE);
g_dwProtectedData = 5;
dwTemp = g_dwProtectedData;

__finally {
// 3. Allow others to use protected data.
ReleaseSemaphore(g_hSem, 1, NULL);

// 4. Continue processing.
return (dwTemp);

例1 Funcenstein1函數(shù)代碼
在函數(shù)Funcenstein1中,我們使用了try-finally程序塊。但是它們并沒有為我們做多少工作:等待一個指示燈信號,改變保護數(shù)據(jù)的內(nèi)容,將新的數(shù)據(jù)指定給一個局域變量dwTemp,釋放指示燈信號,返回新的數(shù)據(jù)給調(diào)用函數(shù)。
2.2.2、例2--Funcenstein2
現(xiàn)在讓我們對Funcenstein1稍稍做一些改動,看看會出現(xiàn)什么情況(見清單二)。
DWORD Funcenstein2(void) {
DWORD dwTemp;
// 1. Do any processing here.
.
.
.
__try {
// 2. request permission to access protected data, and then use it.
WaitForSingleObject(g_hSem, INFINITE);
g_dwProtectedData = 5;
dwTemp = g_dwProtectedData;
// Return the new value.
return (dwTemp);

__finally {
// 3. Allow others to use protected data.
ReleaseSemaphore(g_hSem, 1, NULL);

// 4. Continue processing--this code will never execute in this version.
dwTemp = 9;
return (dwTemp);

例2 Funcenstein2函數(shù)代碼
在函數(shù)Funcenstein2中,我們在try程序段里加入了一個return返回語句。該返回語句告訴編譯器,你想離開函數(shù)Funcenstein2并返回dwTemp內(nèi)的內(nèi)容5給調(diào)用函數(shù)。然而,如果此返回語句被執(zhí)行,本線程永遠不會釋放指示燈信號,其它線程也就永遠不會得到該指示燈信號。你可以想象,在多線程程序中這是一個多么嚴重的問題。
但是,使用了中斷處理句柄避免了這種情況發(fā)生。當(dāng)返回語句試圖離開try程序段時,編譯器保證了在finally程序段內(nèi)的代碼得到執(zhí)行。所以,finally程序段內(nèi)的代碼保證會在try程序段中的返回語句前執(zhí)行。在函數(shù)Funcenstein2中,將調(diào)用ReleaseSemaphore放在finally程序段內(nèi)保證了指示燈信號會得到釋放。
在finally程序段內(nèi)的代碼被執(zhí)行后,函數(shù)Funcenstein2立即返回。這樣,因為try程序段內(nèi)的return返回語句,任何finally程序段后的代碼都不會被執(zhí)行。因而Funcenstein2返回值是5,而不是9。
必須指出的是,當(dāng)遇到例2中這種過早返回語句時,編譯器需要產(chǎn)生額外的代碼以保證finally程序段內(nèi)的代碼的執(zhí)行。此過程稱作為局域展開。當(dāng)然,這必然會降低整個程序的效率。所以,你應(yīng)該盡量避免使用這類代碼。在后面我們會討論關(guān)鍵詞__leave,它可以幫助我們避免編寫出現(xiàn)局域展開一類的代碼。
2.2.3、例3--Funcenstein3
現(xiàn)在讓我們對Funcenstein2做進一步改動,看看會出現(xiàn)什么情況(見例3)。
DWORD Funcenstein3(void) {
DWORD dwTemp;
// 1. Do any processing here.
.
.
.
__try {
// 2. request permission to access protected data, and then use it.
WaitForSingleObject(g_hSem, INFINITE);
g_dwProtectedData = 5;
dwTemp = g_dwProtectedData;
// Try to jump over the finally block.
goto ReturnValue;

__finally {
// 3. Allow others to use protected data.
ReleaseSemaphore(g_hSem, 1, NULL);

dwTemp = 9;
// 4. Continue processing.
ReturnValue:
return (dwTemp);

例3 Funcenstein3函數(shù)代碼
在函數(shù)Funcenstein3中,當(dāng)遇到goto語句時編譯器會產(chǎn)生額外的代碼以保證finally程序段內(nèi)的代碼得到執(zhí)行。但是,這一次finally程序段后ReturnValue標簽后面的代碼會被執(zhí)行,因為try或finally程序段內(nèi)沒有返回語句。函數(shù)的返回值是5。同樣,由于goto語句打斷了從try程序段到finally程序段的自然流程,程序的效率會降低。
2.2.4、例4--Funcfurter1
現(xiàn)在讓我們來看中斷處理真正展現(xiàn)其功能的一個例子。(見例4)。
DWORD Funcfurter1(void) {
DWORD dwTemp;
// 1. Do any processing here.
.
.
.
__try {
// 2. request permission to access protected data, and then use it.
WaitForSingleObject(g_hSem, INFINITE);
dwTemp = Funcinator(g_dwProtectedData);

__finally {
// 3. Allow others to use protected data.
ReleaseSemaphore(g_hSem, 1, NULL);

// 4. Continue processing.
return (dwTemp);

例4 Funcfurter1函數(shù)代碼
設(shè)想try程序段內(nèi)調(diào)用的Funcinator函數(shù)具有某種缺陷而造成無效內(nèi)存讀寫。在16位視窗應(yīng)用程序中,這會導(dǎo)致一個已定義好的錯誤信息對話框出現(xiàn)。在用戶關(guān)閉對話框的同時該應(yīng)用程序也終止運行。在不具有try-finally的Win32應(yīng)用程序中,這會導(dǎo)致程序終止運行,指示燈信號永遠不會得到釋放。這就造成了等待該指示燈信號的其它線程會永遠等待下去。而將ReleaseSemaphore放在finally程序段內(nèi)則從根本上保證了不論何種情況出現(xiàn)指示燈信號都會得到釋放。
如果中斷處理句柄能夠處理由于無效內(nèi)存讀寫而造成的程序中斷,我們就完全有理由相信它能夠處理諸如setjump/longjump、break和continue這類的中斷轉(zhuǎn)移。事實也正是這樣。
2.3、小測試
下面一個例子(見清單五)請讀者猜測一下函數(shù)FuncaDoodleDoo的返回值。(答案為14)
DWORD FuncaDoodleDoo(void) {
DWORD dwTemp = 0;
while (dwTemp 〈 10) {
__try {
if (dwTemp == 2)
continue;
if (dwTemp == 3)
break;

__finally {
dwTemp++;

dwTemp++;
}
dwTemp += 10;
return (dwTemp);

FuncaDoodleDoo函數(shù)代碼
雖然中斷處理句柄能夠接收出現(xiàn)在try程序段內(nèi)的絕大部分異常情況,但是如果線程或進程中斷執(zhí)行的話,則finally程序段內(nèi)的代碼不會被執(zhí)行。調(diào)用ExitThread或ExitProcess就會立即造成線程或進程的中斷,而不會執(zhí)行finally程序段。另外,如果其它的應(yīng)用程序調(diào)用ExitThread或ExitProcess而造成你的線程或進程中斷,你程序中的finally程序段也不會被執(zhí)行。一些C函數(shù)如abort會調(diào)用ExitProcess,也會導(dǎo)致你的finally程序段不被執(zhí)行。對此你無能為力。但你可以防止你自己提早調(diào)用ExitThread或ExitProcess。
2.4、應(yīng)用例子
我們已經(jīng)討論了中斷處理句柄的句法及語法,F(xiàn)在我們進一步討論如何利用中斷處理句柄來簡化一個比較復(fù)雜的編程問題。
首先讓我們來看一個沒有使用中斷處理句柄的例子,程序源代碼見例6。
BOOL Funcarama1 (void) {
HANDLE hFile = INVALID_HANDLE_VALUE;
LPVOID lpBuf = NULL;
DWORD dwNumBytesRead;
BOOL fOk;
hFile = CreateFile("SOMEDATA.DAT", GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
return (FALSE);
}
lpBuf = VitualAlloc(NULL, 1024, MEM_COMMIT, PAGE_READWRITE);
if (lpBuf == NULL) {
CloseHandle(hFile);
return (FALSE);
}
fOk = ReadFile(hFile, lpBuf, 1024, &dwNumBytesRead, NULL);
if (!fOk (dwNumBytesRead == 0)) {
VirtualFree(lpBuf, MEM_RELEASE MEM_DECOMMIT);
CloseHandle(hFile);
return (FALSE);
}
// Do some calculation on the data.
.
.
.

// Clean up all the resources.
VirtualFree(lpBuf, MEM_RELEASE MEM_DECOMMIT);
CloseHandle(hFile);
return (TRUE);
}
例6 沒有使用中斷處理句柄的Funcarama1函數(shù)代碼
在上例Funcarama1函數(shù)中,所有的錯誤診斷使得該函數(shù)難以理解、維護和修改。當(dāng)然,我們可以對Funcarama1函數(shù)進行一些改動,使其易于理解(見例7)。
BOOL Funcarama2 (void) {
HANDLE hFile = INVALID_HANDLE_VALUE;
LPVOID lpBuf = NULL;
DWORD dwNumBytesRead;
BOOL fOk, fSuccess = FALSE;
hFile = CreateFile("SOMEDATA.DAT", GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL);
if (hFile != INVALID_HANDLE_VALUE) {
lpBuf = VitualAlloc(NULL, 1024, MEM_COMMIT, PAGE_READWRITE);
if (lpBuf != NULL) {
fOk = ReadFile(hFile, lpBuf, 1024, &dwNumBytesRead, NULL);
if (fOk (dwNumBytesRead != 0)) {
// Do some calculation on the data.
.
.
.
fSuccess = TRUE;
}
}
VirtualFree(lpBuf, MEM_RELEASE MEM_DECOMMIT);
}
CloseHandle(hFile);
return (fSuccess);
}
例7 沒有使用中斷處理句柄的Funcarama2函數(shù)代碼
雖然函數(shù)Funcarama2容易理解,但是仍然難于維護和修改。
現(xiàn)在讓我們來利用中斷處理句柄重寫Funcaram1函數(shù),其代碼如清單八。
BOOL Funcarama3 (void) {
HANDLE hFile = INVALID_HANDLE_VALUE;
LPVOID lpBuf = NULL;
__try {
DWORD dwNumBytesRead;
BOOL fOk;
hFile = CreateFile("SOMEDATA.DAT", GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
return (FALSE);
}
lpBuf = VitualAlloc(NULL, 1024, MEM_COMMIT, PAGE_READWRITE);
if (lpBuf == NULL) {
return (FALSE);
}
fOk = ReadFile(hFile, lpBuf, 1024, &dwNumBytesRead, NULL);
if (!fOk (dwNumBytesRead == 0)) {
VirtualFree(lpBuf, MEM_RELEASE MEM_DECOMMIT);
return (FALSE);
}
// Do some calculation on the data.
.
.
.

__finally {
// Clean up all the resources.
if (lpBuf != NULL)
VirtualFree(lpBuf, MEM_RELEASE MEM_DECOMMIT);
if (hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
}
// Continue processing.
return (TRUE);
}
例8 使用了中斷處理句柄的Funcarama3函數(shù)代碼
Funcarama3函數(shù)版的好處是所有的清除工作都集中在一個地方:finally程序段內(nèi)。這樣在我們需要對該函數(shù)增加新的條件語句時,我們只需要在finally程序段內(nèi)簡單增添一行清除語句就可以了,而不必回過頭來在每一出可能出錯的地方添加清除語句。
Funcarama3函數(shù)的真正問題在于其效率。我們以前說過應(yīng)盡可能的避免在try程序段內(nèi)使用return語句。為了避免這種情況,微軟在它的編譯器里引進了另一個關(guān)鍵詞__leave。利用關(guān)鍵詞__leave重寫的Funcarama3函數(shù)見例9。
BOOL Funcarama4 (void) {
HANDLE hFile = INVALID_HANDLE_VALUE;
LPVOID lpBuf = NULL;
// Assume that the function will not execute successfully.
BOOL fFunctionOk = FALSE;
__try {
DWORD dwNumBytesRead;
BOOL fOk;
hFile = CreateFile("SOMEDATA.DAT", GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
__leave;
}
lpBuf = VitualAlloc(NULL, 1024, MEM_COMMIT, PAGE_READWRITE);
if (lpBuf == NULL) {
__leave;
}
fOk = ReadFile(hFile, lpBuf, 1024, &dwNumBytesRead, NULL);
if (!fOk (dwNumBytesRead == 0)) {
VirtualFree(lpBuf, MEM_RELEASE MEM_DECOMMIT);
__leave;
}
// Do some calculation on the data.
.
.
.
// Indicate that the entire function executed successfully.
fFunctionOk = TRUE;

__finally {
// Clean up all the resources.
if (lpBuf != NULL)
VirtualFree(lpBuf, MEM_RELEASE MEM_DECOMMIT);
if (hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
}
// Continue processing.
return (fFunctionOk);
}
例9 使用了中斷處理句柄和關(guān)鍵詞__leave的Funcarama4函數(shù)代碼
try程序段內(nèi)的關(guān)鍵詞__leave導(dǎo)致程序運行指針直接跳到try程序段的結(jié)尾(你可以將此看成為跳到try程序段的結(jié)束花括弧)。這樣,因為控制流程將“自然”的離開try程序段,進入finally程序段,所以不需付出額外代價而導(dǎo)致效率降低。但是你需要引進一個新的變量來指示整個函數(shù)的運行是否成功。
從try程序段到finally程序段,控制流程既可以是自然進入,也可以是由于異常的出現(xiàn)而導(dǎo)致控制流程過早離開try程序段而進入finally程序段。為確定何種情況下造成finally程序段的運行,我們可以調(diào)用AbnormalTermination函數(shù)來診斷。
BOOL AbnormalTermination(VOID);
該函數(shù)只能在finally程序段內(nèi)調(diào)用以診斷與此finally相對應(yīng)的try程序段是否是過早離開。如果AbnormalTermination的返回值是FALSE,表明程序流程是自然離開try程序段。否則,則是過早離開。
3、異常處理句柄(Exception handler)
3.1、異常(或例外)處理句柄的定義
異常(或例外)是你不希望出現(xiàn)的事件。在一個完好的應(yīng)用程序中,你不希望讀寫無效內(nèi)存地址或除數(shù)為零的情況出現(xiàn)。但是這類錯誤的確會發(fā)生。在出現(xiàn)這類錯誤時,CPU會負責(zé)提出針對該類錯誤的例外。當(dāng)CPU提出一個例外時,我們稱之為硬件異常(或例外)(hardware exception)。操作系統(tǒng)和應(yīng)用程序自身也可以提出自己的異常。這類異常我們稱之為軟件異常(或例外)(software exception)。
當(dāng)一個硬件異;蜍浖惓1惶岢鰰r,操作系統(tǒng)向你的程序提供一種機會使得你的程序可以診斷那類異常被提出并允許你的程序?qū)Υ诉M行處理。異常處理句柄的語法為
__try {
// Guarded body
.
.
.
}
__except (exception filter) {
// Exception handler
.
.
.
}
請注意關(guān)鍵詞__except。當(dāng)你建立一個try程序段時,它必須跟隨一個finally程序段或一個except程序段。一個try程序段不能同時既跟隨一個finally程序段又跟隨一個except程序段。一個try程序段也不能同時跟隨多個finally程序段或多個except程序段。但是,try-finally程序段卻可以嵌套在try-except程序段內(nèi),或try-except程序段嵌套在try-finally程序段內(nèi)。
3.2、幾個例子
不同于中斷處理句柄,異常處理句柄直接由操作系統(tǒng)執(zhí)行,編譯器不需要做太多工作。下面我們通過幾個具體例子來討論異常處理句柄是如何工作的。
3.2.1、例5--Funcmeister1函數(shù)
下面是一個使用了try-except異常處理句柄的函數(shù)Funcmeister1,其代碼見清單十。
DWORD Funcmeister1 (void) {
DWORD dwTemp;
// 1. Do any processing here.
.
.
.
__try {
// 2. Perform some operation.
dwTemp = 0;
}
__except (EXCEPTION_EXECUTE_HANDLER) {
// 3. Handle an exception; this never executes.
.
.
.
}
// 3. Continue processing.
return (dwTemp);
}
例10 例5Funcmeister1函數(shù)代碼
在Funcmeister1函數(shù)中的try程序段內(nèi),我們簡單地將dwTemp賦值為零。該操作不會導(dǎo)致任何異常的提出。所以,except程序段內(nèi)的程序永遠不會被執(zhí)行。請注意,這有別于中斷處理句柄try-finally。在執(zhí)行了dwTemp賦值語句后的下一個執(zhí)行語句是return返回語句。
雖然我們不鼓勵在try程序段內(nèi)使用return, goto, continue和break語句,但是在異常處理句柄的try程序段內(nèi)使用這些語句不會象中斷處理句柄那樣造成運行代碼的增加和效率下降。
3.2.2、例6--Funcmeister2函數(shù)
讓我們對Funcmeister1函數(shù)進行一些改動,看看會出現(xiàn)什么情況。改動后的函數(shù)見例11。
DWORD Funcmeister2 (void) {
DWORD dwTemp = 0;
// 1. Do any processing here.
.
.
.
__try {
// 2. Perform some operation(s).
dwTemp = 5 / dwTemp; // Generate an exception
dwTemp += 10; // Never excutes
}
__except ( /* 3. Evaluate filter. */ EXCEPTION_EXECUTE_HANDLER) {
// 4. Handle an exception; this never executes.
MessageBeep(0);
.
.
.
}

// 5. Continue processing.
return (dwTemp);
}
例11 例6Funcmeister2函數(shù)代碼
函數(shù)Funcmeister2中的try程序段dwTemp = 5 / dwTemp語句導(dǎo)致CPU提出一個硬件異常。當(dāng)該異常被提出時,操作系統(tǒng)會尋找相對應(yīng)的except程序段的起始位置并評估其異常篩選表達式(exception filter expression)。異常篩選表達式可以取下列標識符值之一。這些標識符定義在Win32 EXCPT.H頭文件中。
標識符 定義為
EXCEPTION_EXECUTE_HANDLER 1
EXCEPTION_CONTINUE_SEARCH 0
EXCEPTION_CONTINUE_EXECUTION -1
3.3、異常篩選(exception filter)
EXCEPTION_EXECUTE_HANDLER表明當(dāng)一個異常出現(xiàn)時,運行程序跳到except程序段轉(zhuǎn)而執(zhí)行except程序段內(nèi)的代碼。except程序段內(nèi)的代碼執(zhí)行完后,系統(tǒng)認為該異常已處理完,接著繼續(xù)執(zhí)行except程序段后的代碼。
EXCEPTION_CONTINUE_EXECUTION表明當(dāng)一個異常出現(xiàn)時,運行程序不立即執(zhí)行except程序段內(nèi)的代碼而返回try程序段內(nèi)產(chǎn)生異常的語句繼續(xù)執(zhí)行該語句。
EXCEPTION_CONTINUE_SEARCH表明當(dāng)一個異常出現(xiàn)時,運行程序不執(zhí)行該except程序段內(nèi)的代碼而尋求由高一級的異常處理句柄來處理此異常。
Win32 WINBASE.H頭文件中定義了可能出現(xiàn)的各種異常代碼。我們可以通過調(diào)用GetExceptionCode函數(shù)來診斷何種異常被提出,從而決定異常處理句柄該采取何種行動。GetExceptionCode函數(shù)定義為
DWORD GetExceptionCode(VOID);
它的返回值表明何種異常出現(xiàn)。下面的程序說明如何調(diào)用GetExceptionCode函數(shù)。
__try {
x = 0;
y = 4 / x;
}
__except ((GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO) ?
EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
// Handle divide by zero exception.
}
當(dāng)一個異常發(fā)生時,操作系統(tǒng)會將有關(guān)該異常的信息儲存在三個結(jié)構(gòu)中,并將它們存放在提出此異常線程的堆棧里。這三個結(jié)構(gòu)是EXCEPTION_RECORD,CONTEXT,和EXCEPTION_POINTERS。EXCEPTION_RECORD儲存著與CPU無關(guān)的異常信息,CONTEXT則儲存著與CPU有關(guān)的異常信息。EXCEPTION_POINTERS結(jié)構(gòu)包含了兩個分別指向EXCEPTION_RECORD和CONTEXT的指針。
typedef struct _EXCEPTION_POINTERS {
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT ContextRecord;
} EXCEPTION_POINTERS
假如你的程序需要這些異常信息,你可以通過調(diào)用GetExceptionInformation函數(shù)來獲取。
LPEXCEPTION GetExceptionInformation(void);
GetExceptionInformation函數(shù)返回一個指向EXCEPTION_POINTERS結(jié)構(gòu)的指針。下面的函數(shù)說明了如何調(diào)用GetExceptionInformation函數(shù)。
void FuncSkunk (void) {
// Declare variables that we can use to save the exception
// record and the context if an exception should occur.
EXCEPTION_RECORD SavedExceptRec;
CONTEXT SavedContext;
.
.
.
__try {
.
.
.
}
__except (
SavedExceptRec =
*(GetExceptionInformation())->ExceptionRecord,
SavedContext =
*(GetExceptionInformation())->ContextRecord,
EXCEPTION_EXECUTE_HANDLER) {
// We can use the SavedExceptRec and SavedContext
// variables inside the handler code block.
switch (SavedExceptRec.ExceptionCode) {
.
.
.
}
}
.
.
.
}
注意,在上面的異常篩選表達式程序中我們使用了C語言的“,”操作符。許多程序員對此并不是很熟悉。該操作符告訴編譯器從左到右運行由“,”分離的各表達式。在所有的表達式都運行完后,返回最后一個(或最右面的)表達式的值。
4、軟件異常(software exception)
至此為止我們所討論的是如何處理由CPU提出的硬件異常(hardware exception)。通常,操作系統(tǒng)或應(yīng)用程序自身提出的軟件異常也非常有用。例如,HeapAlloc函數(shù)就提供了一個非常好的利用軟件異常的例子。在調(diào)用HeapAlloc時,你可以設(shè)置HEAP_GENERATE-EXCEPTIONS指示旗(flag)。這樣如果HeapAlloc不能滿足你的內(nèi)存分配要求,它會產(chǎn)生一個STATUS_NO_MEMORY軟件異常。
假如你想利用這個異常,你可以在你的try程序段內(nèi)繼續(xù)編寫你的代碼,如同內(nèi)存分配總是會成功一樣。如果內(nèi)存分配失敗,你可以利用except程序段來處理這個異;蚶胒inally程序段來做清除工作。
你的程序不需要知道你要處理的異常是軟件異常還是硬件異常。你利用try-finally和try-except來處理軟件異常和硬件異常的方式是一樣的。但是你可以讓你的程序象HeapAlloc函數(shù)一樣提出自己的異常。為了在你的程序中提出軟件異常,你需要調(diào)用RaiseException函數(shù)。
VOID RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags,
DWORD cArguments, LPDWORD lpArguments);
關(guān)于該函數(shù)的使用,請參考微軟的有關(guān)文獻。
5、結(jié)論
結(jié)構(gòu)異常處理由中斷處理和例外處理兩部分組成。采用結(jié)構(gòu)異常處理使得你可以將精力集中在你的程序應(yīng)用代碼設(shè)計上,從而使得應(yīng)用方案的設(shè)計更方便、具體。采用結(jié)構(gòu)異常處理編寫的程序更易于理解、修改和維護,從而增加了程序的可讀性和維護性。