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