[转] 《完美程式设计指南》Effective Delphi
生活随笔
收集整理的這篇文章主要介紹了
[转] 《完美程式设计指南》Effective Delphi
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
Effective Delphi
條款1:不管怎么樣,請讓你的Project至少user一次SysUtils.pas單元
很多使用Delphi的人都對Delphi有著這樣一個抱怨:Delphi雖然開發效率高,但是其編譯出來的程序卻是太大。使用Delphi5新建一個Project然后直接編譯,程序的Size就已經達到了286KB,而如果把同樣的程序放到Delphi7下面編譯的話,那么其Size更是達到了360KB。正是由于這點,所以為Delphi編譯生成的應用程序“減肥”便成為了幾乎所有Delphi社區的一個保留性話題。
其間,大多數人都是使用可執行文件壓縮工具(比如Aspack或者Upx等)來壓縮Delphi所生成的可執行文件以達到“減肥”的目的,但是也有一些人,他們使用一種更為極端,但是更有效的方式來減少Delphi編譯生成的可執行文件的Size,那就是拋棄VCL所提供的編程框架,而直接使用WIN32 SDK加上Object Pascal所提供的面向對象來能來進行程序的撰寫。比如以下一段程序,使用Delphi5的編譯器進行編譯其大小只有16KB,而寫一個基本的帶窗口的Window程序其大小也不會超過25KB(以下這段程序使用Delphi3編譯后會更小,其原因請見下述):
CODE: program SmallPro;
uses
Windows;
{$R *.RES}
begin
MessageBox(0, 'Hello World!', 'Information', MB_OK);
end. [Copy to clipboard]
請大家注意,以上程序是在project文件內直接編譯,所以沒有引用到其它的自定義單元。而所包含的Windows單元則只是為了調用MessageBox API函數而必須包含的。
這個編譯出來的程序實在是太小了,小到它足以對那些熟悉Windows SDK方式編程,而又使用Delphi作為開發工具的人產生一定的誘惑力(我自己就算一個:->)。不知道這種方式是否同樣也對你產生過誘惑力或者已經對你產生了誘惑力,如果是的話,那么先請聽我一句忠告,“請為你的Project Uese上SysUtils.pas單元吧,否則你的程序將失去使用異常機制的能力,如果你不接這條忠告的話,你早晚會為你的行為任出代價。”
關于異常處理,各人的看法不同,有的人認為它是一種極美妙的錯誤處理方式:因為它能夠使程序代碼中處理錯誤部分的代碼與實現邏輯部分的代碼分離,使程序的源代碼變得更優雅且撰寫起來更方便和易讀。而有人則認為使用異常機制來處理程序中的錯誤是不好的行為:因為一旦異常被觸發,并且你未對其加控制的話,那么這個異常將導致應用程序終止,這種錯誤處理方式太過直接和粗魯。但是,不管怎樣,無庸置疑一點的是,你的程序代碼可以不使用異常機制來處理錯誤,但是你卻無法預計在你的代碼當中所調用的各種庫函數或者類是否使用或者支持異常機制,所以為了保證你程序的魯棒性,即使你的代碼不使用異常機制,那么你也應該在你代碼的關鍵位置,加入異常處理的代碼,以免你的代碼所調用的其它代碼或者操作系統拋出異常,導致程序意外的終止。下面便是一個簡單的小例了:
CODE: program SmallPro;
uses
Windows,
SysUtils;
{$R *.RES}
var
p: PChar;
begin
try
p := nil;
p^ := 'l';
except
MessageBox(0, 'Exception', 'Information', MB_OK);
end;
end. [Copy to clipboard]
以上程序向地址空間0x00000000寫一個字節的數據,在現在所有版本的Windows操作系統下面,這都將被系統視為非法操作,所以操作系統會拋出一個SHE異常,而Delphi的RTL系統會使你的程序能夠欄截住這個異常并加以處理,如果你的程序沒有處理這個異常的話,那么Delphi的RTL會彈出一個顯示異常信息的對話框,并在你按下對話框的“確定”按鈕后終止整個程序。我們上面的程序處理了這個異常,程序將在彈出MessageBox函數所顯示的對話框后繼續執行try…except.塊后面的代碼。
下面我們將上面的這個例子做一個很小的改動,將uses的SysUtils.pas單元去掉,然后再運行看看會出現什么樣結果。
程序執行的結果和uses了SysUtils.pas單元的版本有著相當大的差異,程序只會顯示一個如下圖所示的:Runtime Error的對話框,然后便終止運行了,我們的異常處理塊try..except則根本就沒有起到作用。
(圖1:運行時錯誤)
經過以上的測試,我想你已經能夠明白,如果想讓你的使用Delphi編譯器所編譯出來的程序能夠支持異常機制的話,那么你就必須去在你的項目當中至少的包含的一次SysUtils.pas單元。寫到此處,此條款應該可以說是功德圓滿,但是我想我還是有必要帶你簡單的了解一下Delphi的整個異常處理機制,以便你能夠對SysUtils.pas單元在整個Delphi異常機制中所占的地位有一個進一步的認識,并能夠做到更好的使用它。
追根溯源,Delphi的編譯器其實會向C/C++編譯器一樣為你的程序在鏈接時插入一段啟動代碼來使操作系統能夠調用它,并啟動整個應用程序(這個不是C/C++的main函數,如果你對這方面感興趣的話,我建議你去讀Jeffry Richter所著的《Programming Applications for Microsoft Windows Fourth Edition》,這本書的第4章對Processes的講述中有相關的描述)。
對于以EXE形式存在的和以DLL形式存在的程序來說,Delphi為它們插入的啟動代碼的名稱是不一樣的,對于EXE型程序來說,Delphi會為你的程序插入其一個名稱為_InitExe的過程,而對于DLL型程序來說,Delphi編譯器會為你的程序插入一個名稱為_InitLib的過程,你可以從SysInit.pas單元的源代碼當中找到這兩個過程的定義和實現。說到這里順便提一句,System.pas和SysInit.pas兩個Pascal單元是Delphi編譯器在編譯程序時默認包含的兩個單元(你從來沒有見到過哪一個程序uses過這兩個單元吧)。而Delphi的每一個版本幾乎都會對這兩個單元進行擴展和修改,也正因為這個原因,所以在前面你看到的使用Delphi7編譯的那個小程序的Size要比Delphi5編譯出來的同樣程序大的多。
在_InitExe過程的內部會調用一個名稱為_StartExe的過程,而在這個_StartExe過程的內部中則會去調用在System.pas單元中定義的SetExceptionHandler函數來初始化整個Delphi的異常處理機制,在這個過程中設置的_ExceptionHandler過程則正是Delphi整個異常處理機制的核心處理過程。
在_ExceptionHandler會使用到System.pas單元中定義的一系列過程指針變量(比如ExceptProc,ExceptClsProc,ExceptObjProc等),這些過程指針變量都是Delphi整個異常機制當中必須的使用到的,而這些變量的初始化工作便是在SysUtils.pas單元中定義的InitExceptions單元中,InitExceptions變量會在SysUtils單元的initialization部分被調用,于是整個Delphi的異常處理機制便初始化完成。對于DLL型的程序,其異常處理過程的初始化部分與EXE型的程序一樣,所以在這里就不再復述了。
好了在介紹了Delphi最核心的異常處理過程之后,我們再來介紹一下這些異常處理過程是如何被觸發的。
當是一個異常被觸發后,操作系統會最先攔截到這個異常。在操作系統攔截到這個異常后,它會馬上調用.KiUserExceptionDispatcher函數(注1),這個函數是的Windows操作系統自身使用的異常處理函數,而在KiUserExceptionDispathcher函數調用的過程中,它會通過某種回調機制,最終去調用我們上面提到過的_ExceptionHandler過程,展開異常并處理之,如果沒有找到如果被拋出異常所匹配的異常,那么則調用在SysUtils.pas中被賦值的ExceptHandler過程指針變量,拋出一個出現異常信息的話框,并在用戶按下確定按鈕之后終止程序。
這里面值得一的是,如果ExceptHandler過程指針變量的值為Nil,那么Delphi的RTL會去調用System.pas單元中定義的MapToRunError過程來做一個異常類型到運行時錯誤碼的映射,并最終調用RunErrorAt過程在顯示運行時錯誤對話框(如圖1所示)終止整個程序的運行。
注1:我的操作系統是WIN2K所以KiUserExceptionDispatcher在ntdll當中,由于條件有限我沒有在WIN98下做過類似的調試,不知道在WIN98下面是否也使用類似的方式來處理異常。另由于KiUserExceptionDispatcher函數微軟未文檔化的一個函數,所以我在這里不太方便對此函數的運作機制進行剖析(因為不同的操作系統中此函數的實現機制可能會不同),所以在這里還請您見諒。
條款1:不管怎么樣,請讓你的Project至少user一次SysUtils.pas單元
很多使用Delphi的人都對Delphi有著這樣一個抱怨:Delphi雖然開發效率高,但是其編譯出來的程序卻是太大。使用Delphi5新建一個Project然后直接編譯,程序的Size就已經達到了286KB,而如果把同樣的程序放到Delphi7下面編譯的話,那么其Size更是達到了360KB。正是由于這點,所以為Delphi編譯生成的應用程序“減肥”便成為了幾乎所有Delphi社區的一個保留性話題。
其間,大多數人都是使用可執行文件壓縮工具(比如Aspack或者Upx等)來壓縮Delphi所生成的可執行文件以達到“減肥”的目的,但是也有一些人,他們使用一種更為極端,但是更有效的方式來減少Delphi編譯生成的可執行文件的Size,那就是拋棄VCL所提供的編程框架,而直接使用WIN32 SDK加上Object Pascal所提供的面向對象來能來進行程序的撰寫。比如以下一段程序,使用Delphi5的編譯器進行編譯其大小只有16KB,而寫一個基本的帶窗口的Window程序其大小也不會超過25KB(以下這段程序使用Delphi3編譯后會更小,其原因請見下述):
CODE: program SmallPro;
uses
Windows;
{$R *.RES}
begin
MessageBox(0, 'Hello World!', 'Information', MB_OK);
end. [Copy to clipboard]
請大家注意,以上程序是在project文件內直接編譯,所以沒有引用到其它的自定義單元。而所包含的Windows單元則只是為了調用MessageBox API函數而必須包含的。
這個編譯出來的程序實在是太小了,小到它足以對那些熟悉Windows SDK方式編程,而又使用Delphi作為開發工具的人產生一定的誘惑力(我自己就算一個:->)。不知道這種方式是否同樣也對你產生過誘惑力或者已經對你產生了誘惑力,如果是的話,那么先請聽我一句忠告,“請為你的Project Uese上SysUtils.pas單元吧,否則你的程序將失去使用異常機制的能力,如果你不接這條忠告的話,你早晚會為你的行為任出代價。”
關于異常處理,各人的看法不同,有的人認為它是一種極美妙的錯誤處理方式:因為它能夠使程序代碼中處理錯誤部分的代碼與實現邏輯部分的代碼分離,使程序的源代碼變得更優雅且撰寫起來更方便和易讀。而有人則認為使用異常機制來處理程序中的錯誤是不好的行為:因為一旦異常被觸發,并且你未對其加控制的話,那么這個異常將導致應用程序終止,這種錯誤處理方式太過直接和粗魯。但是,不管怎樣,無庸置疑一點的是,你的程序代碼可以不使用異常機制來處理錯誤,但是你卻無法預計在你的代碼當中所調用的各種庫函數或者類是否使用或者支持異常機制,所以為了保證你程序的魯棒性,即使你的代碼不使用異常機制,那么你也應該在你代碼的關鍵位置,加入異常處理的代碼,以免你的代碼所調用的其它代碼或者操作系統拋出異常,導致程序意外的終止。下面便是一個簡單的小例了:
CODE: program SmallPro;
uses
Windows,
SysUtils;
{$R *.RES}
var
p: PChar;
begin
try
p := nil;
p^ := 'l';
except
MessageBox(0, 'Exception', 'Information', MB_OK);
end;
end. [Copy to clipboard]
以上程序向地址空間0x00000000寫一個字節的數據,在現在所有版本的Windows操作系統下面,這都將被系統視為非法操作,所以操作系統會拋出一個SHE異常,而Delphi的RTL系統會使你的程序能夠欄截住這個異常并加以處理,如果你的程序沒有處理這個異常的話,那么Delphi的RTL會彈出一個顯示異常信息的對話框,并在你按下對話框的“確定”按鈕后終止整個程序。我們上面的程序處理了這個異常,程序將在彈出MessageBox函數所顯示的對話框后繼續執行try…except.塊后面的代碼。
下面我們將上面的這個例子做一個很小的改動,將uses的SysUtils.pas單元去掉,然后再運行看看會出現什么樣結果。
程序執行的結果和uses了SysUtils.pas單元的版本有著相當大的差異,程序只會顯示一個如下圖所示的:Runtime Error的對話框,然后便終止運行了,我們的異常處理塊try..except則根本就沒有起到作用。
(圖1:運行時錯誤)
經過以上的測試,我想你已經能夠明白,如果想讓你的使用Delphi編譯器所編譯出來的程序能夠支持異常機制的話,那么你就必須去在你的項目當中至少的包含的一次SysUtils.pas單元。寫到此處,此條款應該可以說是功德圓滿,但是我想我還是有必要帶你簡單的了解一下Delphi的整個異常處理機制,以便你能夠對SysUtils.pas單元在整個Delphi異常機制中所占的地位有一個進一步的認識,并能夠做到更好的使用它。
追根溯源,Delphi的編譯器其實會向C/C++編譯器一樣為你的程序在鏈接時插入一段啟動代碼來使操作系統能夠調用它,并啟動整個應用程序(這個不是C/C++的main函數,如果你對這方面感興趣的話,我建議你去讀Jeffry Richter所著的《Programming Applications for Microsoft Windows Fourth Edition》,這本書的第4章對Processes的講述中有相關的描述)。
對于以EXE形式存在的和以DLL形式存在的程序來說,Delphi為它們插入的啟動代碼的名稱是不一樣的,對于EXE型程序來說,Delphi會為你的程序插入其一個名稱為_InitExe的過程,而對于DLL型程序來說,Delphi編譯器會為你的程序插入一個名稱為_InitLib的過程,你可以從SysInit.pas單元的源代碼當中找到這兩個過程的定義和實現。說到這里順便提一句,System.pas和SysInit.pas兩個Pascal單元是Delphi編譯器在編譯程序時默認包含的兩個單元(你從來沒有見到過哪一個程序uses過這兩個單元吧)。而Delphi的每一個版本幾乎都會對這兩個單元進行擴展和修改,也正因為這個原因,所以在前面你看到的使用Delphi7編譯的那個小程序的Size要比Delphi5編譯出來的同樣程序大的多。
在_InitExe過程的內部會調用一個名稱為_StartExe的過程,而在這個_StartExe過程的內部中則會去調用在System.pas單元中定義的SetExceptionHandler函數來初始化整個Delphi的異常處理機制,在這個過程中設置的_ExceptionHandler過程則正是Delphi整個異常處理機制的核心處理過程。
在_ExceptionHandler會使用到System.pas單元中定義的一系列過程指針變量(比如ExceptProc,ExceptClsProc,ExceptObjProc等),這些過程指針變量都是Delphi整個異常機制當中必須的使用到的,而這些變量的初始化工作便是在SysUtils.pas單元中定義的InitExceptions單元中,InitExceptions變量會在SysUtils單元的initialization部分被調用,于是整個Delphi的異常處理機制便初始化完成。對于DLL型的程序,其異常處理過程的初始化部分與EXE型的程序一樣,所以在這里就不再復述了。
好了在介紹了Delphi最核心的異常處理過程之后,我們再來介紹一下這些異常處理過程是如何被觸發的。
當是一個異常被觸發后,操作系統會最先攔截到這個異常。在操作系統攔截到這個異常后,它會馬上調用.KiUserExceptionDispatcher函數(注1),這個函數是的Windows操作系統自身使用的異常處理函數,而在KiUserExceptionDispathcher函數調用的過程中,它會通過某種回調機制,最終去調用我們上面提到過的_ExceptionHandler過程,展開異常并處理之,如果沒有找到如果被拋出異常所匹配的異常,那么則調用在SysUtils.pas中被賦值的ExceptHandler過程指針變量,拋出一個出現異常信息的話框,并在用戶按下確定按鈕之后終止程序。
這里面值得一的是,如果ExceptHandler過程指針變量的值為Nil,那么Delphi的RTL會去調用System.pas單元中定義的MapToRunError過程來做一個異常類型到運行時錯誤碼的映射,并最終調用RunErrorAt過程在顯示運行時錯誤對話框(如圖1所示)終止整個程序的運行。
注1:我的操作系統是WIN2K所以KiUserExceptionDispatcher在ntdll當中,由于條件有限我沒有在WIN98下做過類似的調試,不知道在WIN98下面是否也使用類似的方式來處理異常。另由于KiUserExceptionDispatcher函數微軟未文檔化的一個函數,所以我在這里不太方便對此函數的運作機制進行剖析(因為不同的操作系統中此函數的實現機制可能會不同),所以在這里還請您見諒。
轉載于:https://www.cnblogs.com/temptation/archive/2006/04/25/384333.html
總結
以上是生活随笔為你收集整理的[转] 《完美程式设计指南》Effective Delphi的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [收藏]整理了一些T-SQL技巧
- 下一篇: 使用GDI+缩放图片文件