Windows核心编程 第2 5章 未处理异常和C ++异常(上)
未處理異常和C?+?+異常(上)
? ? 前一章討論了當(dāng)一個異常過濾器返回?E?X?C?E?P?T?I?O?N?_?C?O?N?T?I?N?U?E?_?S?E?A?R?C?H時會發(fā)生什么事情。返回EXCEPTION_CONTINUE_SEARCH?是告訴系統(tǒng)繼續(xù)上溯調(diào)用樹,去尋找另外的異常過濾器。但是當(dāng)每個過濾器都返回E?X?C?E?P?T?I?O?N?_?C?O?N?T?I?N?U?E?_?S?E?A?R?C?H時會出現(xiàn)什么情況呢?在這種情況下,就出現(xiàn)了所謂的“未處理異常”(Unhandled?exception)。
在第6章里我們已經(jīng)知道,每個線程開始執(zhí)行,實際上是利用?K?e?r?n?e?l?3?2?.?d?l?l中的一個函數(shù)來調(diào)用B?a?s?e?P?r?o?c?e?s?s?S?t?a?r?t或B?a?s?e?T?h?r?e?a?d?S?t?a?r?t。這兩個函數(shù)實際是一樣的,區(qū)別在于一個函數(shù)用于進(jìn)程的主線程(Primary?thread):
另一個函數(shù)用于進(jìn)程的所有輔助線程(Secondary?thread):
? ? 注意這兩個函數(shù)都包含一個S?E?H框架。每個函數(shù)都有一個t?r?y塊,并從這個t?r?y塊里調(diào)用主線程或輔助線程的進(jìn)入點函數(shù)。所以,當(dāng)線程引發(fā)一個異常,所有過濾器都返回?E?X?C?E?P?T?I?O?N?_C?O?N?T?I?N?U?E?_?S?E?A?R?C?H時,將會自動調(diào)用一個由系統(tǒng)提供的特殊過濾器函數(shù):?U?n?h?a?n?d?l?e?dE?x?c?e?p?t?i?o?n?F?i?l?t?e?r。
? ? 這個函數(shù)負(fù)責(zé)顯示一個消息框,指出有一個進(jìn)程的線程存在未處理的異常,并且能讓用戶
結(jié)束或調(diào)試這個進(jìn)程。
下面是寫個測試代碼來看下:
測試代碼,引發(fā)異常,但是沒有用__except去處理。根據(jù)上面的那個創(chuàng)建進(jìn)程的函數(shù),會先調(diào)用U?n?h?a?n?d?l?e?dE?x?c?e?p?t?i?o?n?F?i?l?t?e?r彈個窗,點擊關(guān)閉,然后全局展開,執(zhí)行finally的代碼。
25.1?即時調(diào)試
? ? 隨時將調(diào)試程序連接到任何進(jìn)程的能力稱為即時調(diào)試(Just-in-time?Debugging)。這里我們對它如何工作稍加說明:當(dāng)程序員點擊?C?a?n?c?e?l按鈕,就是告訴U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r函數(shù)對進(jìn)程進(jìn)行調(diào)試。在內(nèi)部,U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r調(diào)用調(diào)試程序,這需要查看下面的注冊表子關(guān)鍵字:
在這個子關(guān)鍵字里,?有一個名為D?e?b?u?g?g?e?r的數(shù)值,在安裝Visual?Studio時被設(shè)置成下面的值:
????這一行代碼是告訴系統(tǒng)要將哪一個程序(這里是?M?S?D?e?v.???e?x?e)作為調(diào)試程序運行。當(dāng)然也可以選擇其他調(diào)試程序。U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r還在這個命令行中向調(diào)試程序傳遞兩個參數(shù)。第一個參數(shù)是被調(diào)試進(jìn)程的?I?D。第二個參數(shù)規(guī)定一個可繼承的手工復(fù)位事件,這個事件是由U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r按無信號狀態(tài)建立的。廠商必須實現(xiàn)他們的調(diào)試程序,這樣才能認(rèn)識指定進(jìn)程I?D和事件句柄的-?p和-?e選項。
?????在進(jìn)程?I?D和事件句柄都合并到這個串中之后,?U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r通過調(diào)用C?r?e?a?t?e?P?r?o?c?e?s?s來執(zhí)行調(diào)試程序。?這時,調(diào)試程序進(jìn)程開始運行并檢查它的命令行參數(shù)。如果存在-?p選項,調(diào)試程序取得進(jìn)程I?D,并通過調(diào)用D?e?b?u?g?A?c?t?i?v?e?P?r?o?c?e?s?s將自身掛接在該進(jìn)程上。
BOOL?DebugActiveProcess(DWORD?dwProcessID);
???一旦調(diào)試程序完成自身的掛接,操作系統(tǒng)將被調(diào)試者(d?e?b?u?g?g?e?e)的狀態(tài)通報給調(diào)試程序。例如,系統(tǒng)將告訴調(diào)試程序,在被調(diào)試的進(jìn)程中有多少線程?哪些?D?D?L加載到被調(diào)試進(jìn)程的地址空間中?調(diào)試程序需要花時間來積累這些數(shù)據(jù),以準(zhǔn)備調(diào)試進(jìn)程。在這些準(zhǔn)備工作進(jìn)行的時候,U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r中的線程必須等待。為此,這要調(diào)用?Wa?i?t?F?o?r?S?i?n?g?l?e?O?b?j?e?c?t函數(shù)并傳遞已經(jīng)建立的手工復(fù)位事件的句柄作為參數(shù)。這個事件是按無信號狀態(tài)建立起來的,所以被調(diào)試進(jìn)程的線程要立即被掛起以等待事件。
? ? 在調(diào)試程序完全初始化之后,它要再檢查它的命令行,找?-?e選項。如果該選項存在,調(diào)試程序取得相應(yīng)的事件句柄并調(diào)用?S?e?t?E?v?e?n?t。調(diào)試程序可以直接使用事件的句柄值,因為事件句柄具有創(chuàng)建的可繼承性,并且被調(diào)試進(jìn)程對?U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r函數(shù)的調(diào)用也使調(diào)試程序進(jìn)程成為一個子進(jìn)程。設(shè)定這個事件將喚醒被調(diào)試進(jìn)程的線程。被喚醒的線程將有關(guān)未處理異常的信息傳遞給調(diào)試程序。調(diào)試程序接收這些通知并加載相應(yīng)的源代碼文件,再將自身放在引發(fā)異常的指令位置上。
? ? 還有,不必在調(diào)試進(jìn)程之前等待異常的出現(xiàn)。可以隨時將一個調(diào)試程序連接在任何進(jìn)程上,只需運行“MSDEV?-p?PID”,其中P?I?D是要調(diào)試的進(jìn)程的?I?D。實際上,利用?Windows?2000Task?Manager,做這些事很容易。當(dāng)觀察P?r?o?c?e?s?s標(biāo)記欄時,可以選擇一個進(jìn)程,點擊鼠標(biāo)右鍵,并選擇D?e?b?u?g菜單選項。這將引起?Task?Manager去查看前面討論過的注冊表子關(guān)鍵字,調(diào)用C?r?e?a?t?e?P?r?o?c?e?s?s,并傳遞所選定的進(jìn)程的?I?D作為參數(shù)。在這里,Task?Manager為事件句柄傳送0值。
嘗試了下那個函數(shù),結(jié)果如下:
25.2?關(guān)閉異常消息框
? ? 有時候,在異常發(fā)生時,你可能不想在屏幕上顯示異常消息框。例如,你可能不想讓這些消息框出現(xiàn)在你產(chǎn)品的發(fā)售版本中。如果出現(xiàn)了消息框,很容易導(dǎo)致最終用戶意外地啟動調(diào)試程序來調(diào)試你的程序。最終用戶只需點擊一下消息框中的?C?a?n?c?e?l按鈕,就進(jìn)入了不熟悉的、令人恐惶的區(qū)域?—?調(diào)試程序。可以使用幾種不同的方法來防止這種消息框的出現(xiàn)。
25.2.1?強制進(jìn)程終止運行
為防止U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r顯示異常消息框,可以調(diào)用下面的S?e?t?E?r?r?o?r?M?o?d?e?l函數(shù),并向它傳遞一個S?E?M?_?N?O?G?P?FA?U?LT?E?R?R?O?R?B?O?X標(biāo)識符:
UINT?SetErrorMode(UINT?fuErrorMode);
然后,當(dāng)調(diào)用U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r函數(shù)來處理異常時,看到已經(jīng)設(shè)置了這個標(biāo)志,就會立即返回E?X?C?E?P?T?I?O?N?_?E?X?E?C?U?T?E?_?H?A?N?D?L?E?R。這將導(dǎo)致全局展開并執(zhí)行B?a?s?e?P?r?o?c?e?s?s?S?t?a?r?t或B?a?s?e?T?h?r?e?a?d?S?t?a?r?t中的處理程序。該處理程序結(jié)束進(jìn)程。
主線程里異常不會崩潰
在其他線程里,也不會崩潰,相當(dāng)于這個設(shè)置是整個進(jìn)程的。
?
25.2.2?包裝一個線程函數(shù)
????使用另外一種辦法也可以避免出現(xiàn)這個消息框,就是針對主線程進(jìn)入點函數(shù)(?m?a?i?n、
w?m?a?i?n、Wi?n?M?a?i?n或w?Wi?n?M?a?i?n)的整個內(nèi)容安排一個t?r?y?-?e?x?c?e?p?t塊。保證異常過濾器的結(jié)果值總是E?X?C?E?P?T?I?O?N?_?E?X?E?C?U?T?E?_?H?A?N?D?L?E?R,這樣就保證異常得到處理,防止了系統(tǒng)再調(diào)用U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r函數(shù)。
????在你的異常處理程序中,你可以顯示一個對話框,在上面顯示一些有關(guān)異常的診斷信息。
用戶可以記錄下這些信息,并通報給你公司的客戶服務(wù)部門,以便能夠找到程序的問題根源。
你應(yīng)該建立這個對話框,這樣用戶只能結(jié)束程序而不能調(diào)用調(diào)試程序。
????這種方法的缺點是它只能捕捉進(jìn)程的主線程中發(fā)生的異常。如果其他線程在運行,并且其中有一個線程發(fā)生了一個未處理異常,系統(tǒng)就要調(diào)用內(nèi)部的?U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r函數(shù)。為了改正這一點,需要在所有的輔助線程進(jìn)入點函數(shù)中包含t?r?y?-?e?x?c?e?p?t塊。
25.2.3?包裝所有的線程函數(shù)
? ? Wi?n?d?o?w?s還提供另外一個函數(shù),S?e?t?U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r,利用它可以按S?E?H格式包裝所有的線程函數(shù):
PTOP_LEVEL_EXCEPTION_FILTER?SetUnhandledExceptionFilter(
PTOP_LEVEL_EXCEPTION_FILTER?pTopLevelExceptionFilter);
? ? 在進(jìn)程調(diào)用這些函數(shù)之后,進(jìn)程的任何線程中若發(fā)生一個未處理的異常,就會導(dǎo)致調(diào)用程序自己的異常過濾器。需要將這個過濾器的地址作為參數(shù)傳遞給?S?e?t?U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r。過濾器函數(shù)原型必須是下面的樣子:
LONG?UnhandledExceptionFilter(PEXCEPTION_POINTERS?pExceptionInfo);
? ? 你可能會注意到這個函數(shù)同U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r函數(shù)的形式是一樣的。程序員可以在自己的異常過濾器中執(zhí)行任何想做的處理,但要返回三個?E?X?C?E?P?T?I?O?N?_?*標(biāo)識符中的一個。表2?5?-?1給出了當(dāng)返回各標(biāo)識符時所發(fā)生的事。
????為了使?U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r函數(shù)再成為默認(rèn)的過濾器,可以調(diào)用?S?e?t?U?n?h?a?n?d?l?e?dE?x?c?e?p?t?i?o?n?F?i?l?t?e?r并傳遞?N?U?L?L給它。而且,每當(dāng)設(shè)置一個新的未處理的異常過濾器時,SetUnhandled?ExceptionFilter就返回以前安裝的異常過濾器的地址。如果?Unhandled?ExceptionF?i?l?t?e?r是當(dāng)前所安裝的過濾器,則這個返回的地址就是?N?U?L?L。????如果你自己的過濾器要返回E?X?C?E?P?T?I?O?N?_?C?O?N?T?I?N?U?E?_?S?E?A?R?C?H,你就應(yīng)該調(diào)用以前安裝的過濾器,其地址通過S?e?t?U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r函數(shù)返回。
以下是一個測試?yán)?#xff1a;
25.2.4?自動調(diào)用調(diào)試程序
? ? 現(xiàn)在再介紹關(guān)閉U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r消息框的最后一種方法。在前面提到的同一個注冊表子關(guān)鍵字里,還有另外一個數(shù)據(jù)值,名為?A?u?t?o。這個值用來規(guī)定U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r是應(yīng)該顯示消息框,還是僅啟動調(diào)試程序。如果?A?u?t?o設(shè)置成1,U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r就不顯示消息框向用戶報告異常,而是立即調(diào)用調(diào)試程序。如果?A?u?t?o子關(guān)鍵設(shè)置成?0,U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r就顯示異常消息框,并按前面描述的那樣操作。
25.3?程序員自己調(diào)用U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r
? ? U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r函數(shù)是一個公開的、文檔完備的?Wi?n?d?o?w?s函數(shù),程序員可以直接在自己的代碼中調(diào)用這個函數(shù)。這里是使用這個函數(shù)的一個例子:
?
? ? 在F?u?n?c?a?d?e?l?i?c函數(shù)中,t?r?y塊中的一個異常導(dǎo)致E?x?p?F?l?t?r函數(shù)被調(diào)用。G?e?t?E?x?c?e?p?t?i?o?n?I?n?f?o?r?m?a?t?i?o?n的返回值作為參數(shù)傳遞給?E?x?p?F?l?t?r函數(shù)。在異常過濾器內(nèi),要確定異常代碼并與?E?X?C?E?P?T?I?O?N?_A?C?C?E?S?S?_?V?I?O?L?AT?I?O?N相比較。如果發(fā)生一個存取違規(guī),異常過濾器改正這個問題,并從過濾器返回E?X?C?E?P?T?I?O?N?_?C?O?N?T?I?N?U?E?_?E?X?E?C?U?T?I?O?N。這個返回值導(dǎo)致系統(tǒng)從最初引起異常的指令繼續(xù)執(zhí)行。
如果發(fā)生了其他異常,E?x?p?F?l?t?r調(diào)用U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r,將E?X?C?E?P?T?I?O?N?_?P?O?I?N?T?E?R?S結(jié)構(gòu)的地址傳遞給它作為參數(shù)。U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r顯示消息框,可使程序員結(jié)束進(jìn)程或開始調(diào)試進(jìn)程。U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r的返回值再由E?x?p?F?l?t?r返回。
?
?
?
?
?
總結(jié)
以上是生活随笔為你收集整理的Windows核心编程 第2 5章 未处理异常和C ++异常(上)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windows核心编程 第2 4章 异常
- 下一篇: Windows PE 重定位表编程(枚举