? ? ?對程序錯誤的處理在開始介紹Microsoft Windows 的特性之前,必須首先了解Wi n d o w s的各個(gè)函數(shù)是如何進(jìn)行錯誤處理的。當(dāng)調(diào)用一個(gè)Wi n d o w s函數(shù)時(shí),它首先要檢驗(yàn)傳遞給它的的各個(gè)參數(shù)的有效性,然后再設(shè)法執(zhí)行任務(wù)。如果傳遞了一個(gè)無效參數(shù),或者由于某種原因無法執(zhí)行這項(xiàng)操作,那么操作系統(tǒng)就會返回一個(gè)值,指明該函數(shù)在某種程度上運(yùn)行失敗了。表 1 - 1列出了大多數(shù)Wi n d o w s函數(shù)使用的返回值的數(shù)據(jù)類型。表1-1 Wi n d o w s函數(shù)常用的返回值類型數(shù) 據(jù) 類 型 表示失敗的值V O I D 該函數(shù)的運(yùn)行不可能失敗。Wi n d o w s函數(shù)的返回值類型很少是V O I DB O O L 如果函數(shù)運(yùn)行失敗,那么返回值是0,否則返回的是非0值。最好對返回值進(jìn)行測試,以確定它是0還是非0。不要測試返回值是否為T R U EH A N D L E 如果函數(shù)運(yùn)行失敗,則返回值通常是N U L L,否則返回值為H A N D L E,用于標(biāo)識你可以操作的一個(gè)對象。注意,有些函數(shù)會返回一個(gè)句柄值I N VALID_ HANDLE_VA L U E,它被定義為- 1。函數(shù)的Platform SDK文檔將會清楚地說明該函數(shù)運(yùn)行失敗時(shí)返回的是N U L L還是I N VA L I D _ H A N D L E _ VA L I DP V O I D 如果函數(shù)運(yùn)行失敗,則返回值是N U L L,否則返回P V O I D,以標(biāo)識數(shù)據(jù)塊的內(nèi)存地址L O N G / D W O R D 這是個(gè)難以處理的值。返回?cái)?shù)量的函數(shù)通常返回 L O N G或D W O R D。如果由于某種原因,函數(shù)無法對想要進(jìn)行計(jì)數(shù)的對象進(jìn)行計(jì)數(shù),那么該函數(shù)通常返回 0或- 1(根據(jù)函數(shù)而定)。如果調(diào)用的函數(shù)返回了L O N G / D W O R D,那么請認(rèn)真閱讀Platform SDK文檔,以確保能正確檢查潛在的錯誤
一個(gè)Wi n d o w s函數(shù)返回的錯誤代碼對了解該函數(shù)為什么會運(yùn)行失敗常常很有用。 M i c r o s o f t公司編譯了一個(gè)所有可能的錯誤代碼的列表,并且為每個(gè)錯誤代碼分配了一個(gè) 3 2位的號碼。從系統(tǒng)內(nèi)部來講,當(dāng)一個(gè)Wi n d o w s函數(shù)檢測到一個(gè)錯誤時(shí),它會使用一個(gè)稱為線程本地存儲器(thread-local storage)的機(jī)制,將相應(yīng)的錯誤代碼號碼與調(diào)用的線程關(guān)聯(lián)起來(線程本地存儲器將在第2 1章中介紹)。這將使線程能夠互相獨(dú)立地運(yùn)行,而不會影響各自的錯誤代碼。當(dāng)函數(shù)返回時(shí),它的返回值就能指明一個(gè)錯誤已經(jīng)發(fā)生。若要確定這是個(gè)什么錯誤,請調(diào)用G e t L a s t E r r o r函數(shù):該函數(shù)只返回線程的3 2位錯誤代碼。當(dāng)你擁有3 2位錯誤代碼的號碼時(shí),必須將該號碼轉(zhuǎn)換成更有用的某種對象。 Wi n E r r o r. h頭文件包含了M i c r o s o f t公司定義的錯誤代碼的列表。下面顯示了該列表的某些內(nèi)容,使你能夠看到它的大概樣子:
免使用這個(gè)號碼,可使用消息I D)。請記住,這里只顯示了Wi n E r r o r. h頭文件中的很少一部分內(nèi)容,整個(gè)文件的長度超過2 1 0 0 0行。當(dāng)Wi n d o w s函數(shù)運(yùn)行失敗時(shí),應(yīng)該立即調(diào)用G e t L a s t E r r o r函數(shù)。如果調(diào)用另一個(gè)Wi n d o w s函數(shù),它的值很可能被改寫。注意 G e t L a s t E r r o r能返回線程產(chǎn)生的最后一個(gè)錯誤。如果該線程調(diào)用的Wi n d o w s函數(shù)運(yùn)行成功,那么最后一個(gè)錯誤代碼就不被改寫,并且不指明運(yùn)行成功。有少數(shù)Wi n d o w s函數(shù)并不遵循這一規(guī)則,它會更改最后的錯誤代碼;但是 Platform SDK文檔通常指明,當(dāng)函數(shù)運(yùn)行成功時(shí),該函數(shù)會更改最后的錯誤代碼。Wi n d o w s 9 8 許多Windows 98的函數(shù)實(shí)際上是用M i c r o s o f t公司的1 6位Windows 3.1產(chǎn)品產(chǎn)生的1 6位代碼來實(shí)現(xiàn)的。這種比較老的代碼并不通過 G e t L a s t E r r o r之類的函數(shù)來報(bào)告錯誤,而且M i c r o s o f t公司并沒有在Windows 98中修改1 6位代碼,以支持這種錯誤處理方式。對于我們來說,這意味著Windows 98中的許多Wi n 3 2函數(shù)在運(yùn)行失敗時(shí)不能設(shè)置最后的錯誤代碼。該函數(shù)將返回一個(gè)值,指明運(yùn)行失敗,這樣你就能夠發(fā)現(xiàn)該函數(shù)確實(shí)已經(jīng)運(yùn)行失敗,但是你無法確定運(yùn)行失敗的原因。有些Wi n d o w s函數(shù)之所以能夠成功運(yùn)行,其中有許多原因。例如,創(chuàng)建指明的事件內(nèi)核對象之所以能夠取得成功,是因?yàn)槟銓?shí)際上創(chuàng)建了該對象,或者因?yàn)橐呀?jīng)存在帶有相同名字的事件內(nèi)核對象。你應(yīng)搞清楚成功的原因。為了將該信息返回, M i c r o s o f t公司選擇使用最后錯誤代碼機(jī)制。這樣,當(dāng)某些函數(shù)運(yùn)行成功時(shí),就能夠通過調(diào)用 G e t L a d t E r r o r函數(shù)來確定其他的一些信息。對于具有這種行為特性的函數(shù)來說, Platform SDK文檔清楚地說明了G e t L a s t E r r o r函數(shù)可以這樣使用。請參見該文檔,找出C r e a t e E v e nt函數(shù)的例子。進(jìn)行調(diào)試的時(shí)候,監(jiān)控線程的最后錯誤代碼是非常有用的。在Microsoft Visual studio 6.0中,M i c r o s o f t的調(diào)試程序支持一個(gè)非常有用的特性,即可以配置 Wa t c h窗口,以便始終都能顯示線程的最后錯誤代碼的號碼和該錯誤的英文描述。通過選定 Wa t c h窗口中的一行,并鍵入“@ e r r, h r”,就能夠做到這一點(diǎn)。觀察圖1 - 1,你會看到已經(jīng)調(diào)用了C r e a t e F i l e函數(shù)。該函數(shù)返回I N VA L I D _ H A N D L E _ VA L U E(- 1)的H A N D L E,表示它未能打開指定的文件。但是Wa t c h窗口向我們顯示最后錯誤代碼(即如果調(diào)用 G e t L a s t E r r o r函數(shù),該函數(shù)返回的錯誤代碼)是0 x 0 0 0 0 0 0 0 2。該Wa t c h窗口又進(jìn)一步指明錯誤代碼2是指“系統(tǒng)不能找到指定的文件?!蹦銜l(fā)現(xiàn)它與Wi n E r r o r. h頭文件中的錯誤代碼2所指的字符串是相同的。圖1-1 在Visual Studio 6.0的Wa t c h窗口中鍵入“@ e r r, h r”,就可以查看當(dāng)前線程的最后錯誤代碼F o r m a t M e s s a g e函數(shù)的功能實(shí)際上是非常豐富的,在創(chuàng)建向用戶顯示的字符串信息時(shí),它是首選函數(shù)。該函數(shù)之所以有這樣大的作用,原因之一是它很容易用多種語言進(jìn)行操作。該函數(shù)能夠檢測出用戶首選的語言(在Regional Settings Control Panel小應(yīng)用程序中設(shè)定),并返回相應(yīng)的文本。當(dāng)然,首先必須自己轉(zhuǎn)換字符串,然后將已轉(zhuǎn)換的消息表資源嵌入你的 . e x e文件或D L L模塊中,然后該函數(shù)會選定正確的嵌入對象。 E r r o r S h o w示例應(yīng)用程序(本章后面將加以介紹)展示了如何調(diào)用該函數(shù),以便將M i c r o s o f t公司定義的錯誤代碼轉(zhuǎn)換成它的文本描述。有些人常常問我,M i c r o s o f t公司是否建立了一個(gè)主控列表,以顯示每個(gè)Wi n d o w s函數(shù)可能返回的所有錯誤代碼??上?,回答是沒有這樣的列表,而且 M i c r o s o f t公司將永遠(yuǎn)不會建立這樣的一個(gè)列表。因?yàn)樵趧?chuàng)建系統(tǒng)的新版本時(shí),建立和維護(hù)該列表實(shí)在太困難了。建立這樣一個(gè)列表存在的問題是,你可以調(diào)用一個(gè) Wi n d o w s函數(shù),但是該函數(shù)能夠在內(nèi)部調(diào)用另一個(gè)函數(shù),而這另一個(gè)函數(shù)又可以調(diào)用另一個(gè)函數(shù),如此類推。由于各種不同的原因,這些函數(shù)中的任何一個(gè)函數(shù)都可能運(yùn)行失敗。有時(shí),當(dāng)一個(gè)函數(shù)運(yùn)行失敗時(shí),較高級的函數(shù)對它進(jìn)行恢復(fù),并且仍然可以執(zhí)行你想執(zhí)行的操作。為了創(chuàng)建該主控列表, M i c r o s o f t公司必須跟蹤每個(gè)函數(shù)的運(yùn)行路徑,并建立所有可能的錯誤代碼的列表。這項(xiàng)工作很困難。而且,當(dāng)創(chuàng)建系統(tǒng)的新版本時(shí),這些函數(shù)的運(yùn)行路徑還會改變。1.1 定義自己的錯誤代碼前面已經(jīng)說明 Wi n d o w s函數(shù)是如何向函數(shù)的調(diào)用者指明發(fā)生的錯誤,你也能夠?qū)⒃摍C(jī)制用于自己的函數(shù)。比如說,你編寫了一個(gè)希望其他人調(diào)用的函數(shù),你的函數(shù)可能因?yàn)檫@樣或那樣的原因而運(yùn)行失敗,你必須向函數(shù)的調(diào)用者說明它已經(jīng)運(yùn)行失敗。若要指明函數(shù)運(yùn)行失敗,只需要設(shè)定線程的最后的錯誤代碼,然后讓你的函數(shù)返回FA L S E、I N VA L I D _ H A N D L E _ VA L U E、N U L L或者返回任何合適的信息。