Delphi 2007体验!
Delphi 2007體驗!
baidu
內(nèi)容摘要:CodeGear(From Borland) 公司公布了最新的Delphi 2007 For Win32版本號。作為一個 Delphi 的使用者,第一時間下載、安裝并進(jìn)行了體驗,現(xiàn)將一些使用感受記錄例如以下
CodeGear(From Borland) 公司公布了最新的Delphi 2007 For Win32版本號。作為一個 Delphi 的使用者,第一時間下載、安裝并進(jìn)行了體驗,現(xiàn)將一些使用感受記錄例如以下:
注:以下的對照主要是針對Delphi7與Delphi2007,以下列出的部分功能可能在Delphi8/2005/2006中已存在。
1、下載
可在CodeGear官方站點下載試用版,或在VeryCD上尋找ISO,整個安裝文件約1.2G多一些。
2、安裝
Delphi 2007放棄了InstallShield,採用了InstallAware安裝軟件,整個安裝操作非常友好。在安裝結(jié)束時,會提示是否在Delphi啟 動時自己主動檢查更新,建議不要選擇此項,由于安裝完畢后無法禁用。Delphi 2007在開始菜單中建立了自己主動檢查更新的快捷方式。
3、啟動
Delphi 2007的啟動速度沒有傳說中那么快,但和Delphi7相比也差不了多少,整體感覺不錯。
4、界面
Delphi 2007的界面和之前的BDS 8/2005/2006界面風(fēng)格是一樣的,個人感覺不是太好,由于整個IDE顏色偏暗,Visual Studio 2005那種白亮色的界面應(yīng)該更好一些。Splash和Welcome Page作的還是那么粗糙,CodeGear應(yīng)該盡快找個好美工啊。
5、速度
IDE啟動速度還不錯,IDE的反映速度也非常好,尤其是程序的編譯速度,個人感覺比Delphi7還快。
6、返回傳統(tǒng)界面
習(xí) 慣了Delphi7及之前版本號的界面,對Delphi2007的這樣的一體式界面多少有些難以適應(yīng),尤其是窗口設(shè)計器。盡管能夠通過更改Desktop Layout為Classic Undocked讓其與Delphi7有些相像,但卻失去了Delphi7的那種自由設(shè)計的效果。
內(nèi)容摘要:CodeGear(From Borland) 公司公布了最新的Delphi 2007 For Win32版本號。作為一個 Delphi 的使用者,第一時間下載、安裝并進(jìn)行了體驗,現(xiàn)將一些使用感受記錄例如以下
在Delphi2007中,更改一個選項,可讓IDE的窗口設(shè)計器返回Delphi的傳統(tǒng)風(fēng)格:Tools–Options–VCL Designer,取消選中Embedded Designer。
此選項僅僅有在IDE重新啟動后才會生效,生效后整個界面和Delphi7差點兒相同,但組件面板無法達(dá)到傳統(tǒng)界面的效果。但此設(shè)計似乎有BUG,當(dāng)IDE最小化的時候,窗口設(shè)計器卻沒有一同最小化。
7、新增屬性:Application.MainFormOnTaskBar
用Delphi2007新建一project,然后查看project文件的源碼,發(fā)現(xiàn)多一行代碼:
Application.MainFormOnTaskBar := True;
Delphi2007默認(rèn)已將MainForm顯示于任務(wù)欄,而不是之前版本號的Application。這個功能在曾經(jīng)非常多Delphier都討論過,如今Delphi自身支持了。設(shè)計此屬性非常明顯,因該是為了兼容Windows Vista。
當(dāng)然任務(wù)欄的右鍵菜單也發(fā)生了變化:
(Delphi 7 任務(wù)欄右鍵菜單)
(Delphi 2007 任務(wù)欄右鍵菜單)
8、新Project Option: Enable Runtime Themes
該project選項默認(rèn)啟用,用Delphi 2007編寫的程序默認(rèn)將啟用Themes,這是一個非常好的功能,曾經(jīng)必須用組件:Win32-XPManifest。
Delphi 2007 IDE本身、窗口設(shè)計器已支持操作系統(tǒng)Themes。
9、TeeChart升級為了TeeChart Standard 7.10
TeeChart最終升級了新版本號。
10、報表組件
Delphi 2007似乎沒有附帶不論什么報表組件,QuickReport和Rave消失了。
內(nèi)容摘要:CodeGear(From Borland) 公司公布了最新的Delphi 2007 For Win32版本號。作為一個 Delphi 的使用者,第一時間下載、安裝并進(jìn)行了體驗,現(xiàn)將一些使用感受記錄例如以下
11、DBExpress
DBExpress重大升級至v4,架構(gòu)已重寫,使用此技術(shù)的Delphier能夠試試,本人非常少使用。
12、模態(tài)窗口下的窗口閃動
在當(dāng)前窗口用ShowModal顯示一個模態(tài)窗口后,再次點擊當(dāng)前窗口,此時顯示出的模態(tài)窗口會閃動,Delphi 2007編譯的程序最終已能實現(xiàn)此效果,這也是Windows程序的標(biāo)準(zhǔn)效果。
13、Project Clean 功能
在Project Manager中右鍵點擊project名稱,選擇Clean,會自己主動清除project的全部暫時文件和dcu文件。
14、實用的快捷鍵
最終為Build Project和Run Without Dedugging功能設(shè)置了快捷鍵。
15、比Delphi7超強(qiáng)的編輯器
Delphi2007的編輯器功能強(qiáng)大,這也應(yīng)該是放棄Delphi7的重要理由,如輸入Begin,自己主動生成End,代碼重構(gòu),語法實時檢查,顯示行號等。
只是Delphi2007的那個代碼幫助提示信息的窗口真是太丑了。
16、窗口設(shè)計器控件感應(yīng)對齊
窗口設(shè)計器中的控件能夠感應(yīng)對齊,相當(dāng)好的功能啊。
17、中文變量名
如今的Delphi已經(jīng)支持中文變量名了,你能夠試試。
18、新的組件
TTrayIcon、TFlowPanel、TGridPanel三個控件非常實用。Delphi2007新增Vista Dialogs組件,這些組件效果非常好,可是基于Vista API的,所以僅僅能在Windows Vista下使用。
19、TLabel控件可在內(nèi)容超出范圍時顯示省略號
此功能非常實用,尤其是在Label中顯示一個文件路徑時,設(shè)置TLabel的EllipsisPosition屬性就可以。
20、新增了一些實用的屬性
Delphi2007對大多數(shù)常規(guī)組件增加了一些實用的屬性,如Margins、Padding、TForm.PopupMode等,細(xì)致查看一些控件的屬性列表,你就會發(fā)現(xiàn)非常多陌生的屬性,但他們確實都非常實用。
用了Delphi 2007一段時間,但也發(fā)現(xiàn)一些小問題:
1、在Project Manager中更改PAS文件的名稱后,不自己主動更新uses列表中的名稱,呵呵~,這個要求不知道過只是分。
2、TMainMenu組件在窗口設(shè)計器中不可預(yù)覽。
3、編輯器錯誤提示功能會誤報,如Application.MainFormOnTaskBar屬性,有時會提示不存在該屬性。
4、新安裝的組件,不但要在Tool–Options中設(shè)置Library Path,還必須設(shè)置Browseing Path,組件才干被正常使用。
用了N年的Delphi7了,體驗了Delphi2007后覺得真應(yīng)該換換了,綜合來看,Delphi2007是一個非常好的版本號,IDE速度及功能性各方面都已經(jīng)非常優(yōu)秀,你準(zhǔn)備使用Delphi2007嗎?
Delphi 2007 初步印象
baidu
內(nèi)容摘要:經(jīng)過苦苦的等待,最終等來了新一代 Delphi 2007 的下載鏈接。昨天從 emule 上下載時,發(fā)覺有非常多的人在下載,讓我非常感動。原來和我一樣,對 Delphi 關(guān)注的人還不少啊。速度還算快,到夜里就下完了。接著開始了2個小時的試用,盡管不怎么細(xì)致,但從 Delphi 1 一路用過來的我,對里面的變化還比較敏感的。
經(jīng)過苦苦的等待,最終等來了新一代 Delphi 2007 的下載鏈接。昨天從 emule 上下載時,發(fā)覺有非常多的人在下載,讓我非常感動。原來和我一樣,對 Delphi 關(guān)注的人還不少啊。速度還算快,到夜里就下完了。接著開始了2個小時的試用,盡管不怎么細(xì)致,但從 Delphi 1 一路用過來的我,對里面的變化還比較敏感的。
相比 Delphi 2006 來說,我覺得這個版本號的 Delphi 2007 是個超強(qiáng)的優(yōu)化版,功能方面,僅僅有少些改變。說她是優(yōu)化版,那是由于她的啟動速度,編譯效率,IDE 速度是相當(dāng)?shù)目?#xff08;PS:我的電腦是PM1.4G,512MB)。李維先生所說的比 Delphi 7 快一點也不為過。近期用 VS2005,打開一個 C++ 控制臺程序,要經(jīng)過相當(dāng)長的時間,編譯就更不用說了。Welcome Page 里的“Where developers matter”真是讓人感動啊。
至于其它方面的改進(jìn),對我來說,并不是非常有吸引力。Help 系統(tǒng)改用 MSDN 的那套最新的 help,支持了VISTA,可是我的電腦是不能跑了。對 Together 的集成,這個非常實用。控件多了幾個。IntraWeb 變成了 CodeGear 的 VCL 部分了,但卻不支持調(diào)試,這點有些奇怪。數(shù)據(jù)庫接口統(tǒng)一了。特別的一點就是,原來 Borland 的標(biāo)識,如今全變成了 CodeGear。
事實上,我并不了解多少開發(fā),更不知道開發(fā)者究竟須要 Delphi 2007 增加些什么功能。我中心一直覺得 Delhpi 發(fā)展到今天,變化的可能已經(jīng)越來越少。就像 VC 一樣,非常少變動。Win32 RAD 的開發(fā)王者,應(yīng)該還是屬于 Delphi(C++上的RAD工具非常少,即便有,也是對語言進(jìn)行了一些惡心擴(kuò)展),速度,效率,誰能相比。如今唯一的希望是 Delphi 2007 的語言再加強(qiáng)一些,如 template。近期用 C++,一直在學(xué)習(xí) template,所以也希望 Delphi 能跟上時代。
內(nèi)容摘要:本文介紹delph i2007學(xué)習(xí)筆記
如今學(xué)的是delphi 的類,原D7的類我不就不記了,記下與D7不同的地方
a.class abstract 純虛類,不能實例化的類
type
TAbstractClass = class abstract
procedure SomeProcedure;
end;
曾經(jīng)的做法是在 procedure 的后面與 abstract ,如今僅僅移類的說明上,僅僅是意思一樣,就是直觀點少打字 呵呵.
b.class sealed 這個我眼下不知是什么意思,可能是不能繼承的類
type
TAbstractClass = class sealed
procedure SomeProcedure;
end;
c.class const 類的常量,這個地方在D7內(nèi)能夠定類的方法一樣能實現(xiàn)
type
TClassWithConstant = class
public
const SomeConst = 'This is a class constant';
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ShowMessage(TClassWithConstant.SomeConst); //引用時,僅僅寫類名就可能引用,不必實例化
end;
d.class type 類的類型, 在類的層次下能夠定record,class子類什么的,這個將數(shù)據(jù)的集中體現(xiàn)....
type
TClassWithClassType = class
private
type
TRecordWithinAClass = record
SomeField: string;
end;
public
class var
RecordWithinAClass: TRecordWithinAClass;
end;
...
procedure TForm1.FormCreate(Sender: TObject);
begin
TClassWithClassType.RecordWithinAClass.SomeField := 'This is a field of a class type declaration';
ShowMessage(TClassWithClassType.RecordWithinAClass.SomeField);
end;
type
TOuterClass = class
strict private
MyField: Integer;
public
type
TInnerClass = class
public
MyInnerField: Integer;
procedure InnerProc;
end;
procedure OuterProc;
end;
procedure TOuterClass.TInnerClass.InnerProc;
begin
...
end;
讓人期待的Delphi 2007 for Win32
baidu
內(nèi)容摘要:從李維的Blog和一些網(wǎng)友的反饋來看,D2007確實是比較讓人期待的一個版本號。作為Delphi的忠實支持者,我期待著Delphi的逐步回歸,希望她能重登昔日的王者地位!
自從D7之后,Delphi似乎開始走了下坡路,到D2005時,讓非常多人感到了失望,而D2006也是非常不easy才挽回了一點局面。大家都知道如今Delphi跟Borland分家,歸屬于CodeGear繼續(xù)發(fā)展了。只是分出去或許會是好事,這能讓一幫人更為專注地發(fā)展IDE的技術(shù)。Delphi 2007 for Win32的公布會(包括Delphi for PHP和InterBase 2007)前幾天正在密鑼緊鼓地進(jìn)行著,從李維的Blog和一些網(wǎng)友的反饋來看,D2007確實是比較讓人期待的一個版本號。作為Delphi的忠實支持者,我期待著Delphi的逐步回歸,希望她能重登昔日的王者地位!(這次不要當(dāng)VB殺手了,來當(dāng)當(dāng)C#和Java殺手吧.... XD)
Delphi2007 for Win32一些技術(shù)亮點:
1、IDE工具採用.Net2.0來編寫,安裝時候須要.Net FrameWork2.0,但編譯出來的Exe是純正的Win32程序,公布不須要.Net FrameWork2.0。新IDE採用.Net FrameWork2.0是為了更好的統(tǒng)一IDE平臺,利用.Net的反射、泛型等高級特性,節(jié)省編寫IDE時間。新的IDE確實比D7啟動還要快,大概是五六秒時間左右就啟動,跟VS.Net2005差點兒相同。演示中,新的IDE編譯新建project比D7還要快一點。
2、Delphi2007支持Vista界面,封裝了Vsita的新API函數(shù)。
3、Delphi2007有一個功能非常有意思,支持D2006的全部BPL組件。意思是,假設(shè)你用D2006編譯過的BPL,沒有源碼和DCU,也能夠直接安裝在Delphi2007。哈哈,這個是delphi版本號上的史無前例,即使第三方控件包來不及支持Delphi2007,也最好還是礙開發(fā)者從D2006升級到Delphi2007做項目開發(fā)。
4、支持MS Build。比方支持Debug、Release。也支持編譯前和編譯后事件,可調(diào)用BAT文件。比方,你編譯前郵件告訴老板,你要給我加薪50%,否則十分鐘后銷毀源碼。另外,也能夠改動其XML格式,僅僅編譯特定的程序代碼。
5、Delphi2007 for Win32數(shù)據(jù)集控件全面支持Unicode。比方,你的數(shù)據(jù)庫表字段可採用中文名稱作字段。Filter也支持Unicode。唉,為了這個filter支持Unicode,有些人在delphiBBS上苦苦守候了六七年。但delphi2007的UI(可視化界面)還不是全面支持Unicode。比方,你的DBGrid就不能支持中文、韓文(不說狗屁日文)、阿拉伯文等同一時候顯示。原有的Vcl框架大量採用string聲明而不是WideString,據(jù)李維講,CodeGear會將Vcl全面支持Unicode,但要考慮一種最佳的平衡方法。
6、非常棒的DBX4。dbExpress將要統(tǒng)一混亂的數(shù)據(jù)庫連接組件,底層全部改寫。無論在Win32還是Win64上,可兼容本機(jī)代碼和托管代碼。呵呵,舉個樣例,DBX4可支持ADO,也能夠支持ADO.Net。這一點,M$也做不到。DBX4新增加了Connection Pool高級組件。更令人心動的是,開放Driver Source,可自行擴(kuò)展屬性和方法,哈哈,ColreLab公司這回可高價賣不出去dbExpress驅(qū)動了。DBX4也全面支持Uicode。
7、Delphi2007 for Win32支持Ajax技術(shù)—Intraweb9.0。Intraweb9.0組件封裝了Ajax,以事件方式來驅(qū)動程序。Intraweb9.0的Ajax技術(shù)支持?jǐn)帱c跟蹤調(diào)試,簡單到跟你調(diào)試其它delphi程序一樣。Intraweb9.0不愧是封裝javascript的上乘之作。事實上,話又說回來了,這一兩年流行的Ajax技術(shù),事實上無非就是javascript封裝而已。而Intraweb已經(jīng)在六七年將javascript技術(shù)做得爐火純青。更令人汗顏的是,很多所謂的web2.0新技術(shù)調(diào)試Ajax時候,仿佛又回到20多年前的C編程時代,不停地用Printf打印調(diào)試。Ajax技術(shù)也并不是是什么高級技術(shù),僅僅是web編程一種無奈的選擇。不久未來,應(yīng)該是屬于智能client平臺。
個人的幾點看法:
1、說實話這里我經(jīng)歷過大喜->大悲->大喜的三次變化。最初聽說Delphi重回Win32,當(dāng)然是大喜,個人比較不爽.Net,由于給別人敲代碼還要別人裝無用的東西才干運(yùn)行(當(dāng)然對象是企業(yè)的話,沒什么所謂);之后在CSDN聽到一些殘缺不全的消息說D2007又要裝.Net了?轉(zhuǎn)而失望;幸而從這里看,僅僅是裝.Net的開發(fā)時環(huán)境而已,運(yùn)行庫并不須要,至此疑慮全消。并且聽說D2007還能針對2000、XP和Vista多種系統(tǒng)公布不同的程序?這個功能太棒了!曾經(jīng)寫的程序,總是部分人能用部分人不能,想出多版本號的話,自己多裝個系統(tǒng)在那個系統(tǒng)下編譯吧,這不是一般的麻煩。希望此功能是真的。
2、啟動和編譯速度比D7還快,這個也非常吸引人。Delphi的編譯速度本來就非常有名了(用過C系列的就能對照出來)。之后聽說D2006又用一種新技術(shù)改進(jìn)了內(nèi)存管理,可惜D2006還沒裝,沒能體驗。這下D2007竟然比D7還快了,希望不是僅僅針對特殊項目弄出來的“演示效果”。
3、關(guān)于Unicode,強(qiáng)烈期待實現(xiàn)整個IDE環(huán)境的Unicode化,某次寫跟韓文有關(guān)的程序已經(jīng)被整慘過了.... -_,- 這點Delphi須要向C#靠攏。
4、盡管IntraWeb自上次寫日志來,還暫時沒安排到學(xué)習(xí)時間。只是偶還是相信那位Delphi達(dá)人的話,相信它的前途。如今都出到9.0了,我覺得假設(shè)Delphi在B/S上面能搶回份額,微軟將會面臨非常大的挑戰(zhàn)。
Delphi 2007 下安裝 Shell 組件
baidu
內(nèi)容摘要:本文介紹Delphi 2007 下安裝 Shell 組件
Delphi 2007 中沒有 Shell 組件,但在Delphi的lib文件夾存在相關(guān)的源文件,并且在C:WindowsSystem32文件夾下存在vclshlctrls100.bpl的運(yùn)行時包,只有沒有設(shè)計時包。
但在Delphi的Demo中有一個ShellControls,安裝其文件夾下的 dclshlctrls.dpk 就可以安裝成功Shell組件。
注1:Shell組件安裝后還是在組件面板的Sample頁中;
注2:Delphi2007的Demo文件夾在:我的電腦-共享文檔-RAD Studio-Demos-DelphiWin32-VCLWin32;
在 使用Shell這樣的組件時,程序編譯后都會出現(xiàn)以下的警告:[DCC Warning] Unit1.pas(7): W1005 Unit ‘ShellCtrls’ is specific to a platform ,假設(shè)您不想讓他顯示的話,能夠在你project相關(guān)源文件的開頭加上以下代碼:
{$WARN UNIT_PLATFORM OFF}
Delphi歸來,重燃開發(fā)者的信心
baidu
內(nèi)容摘要:江元麟表示:「眼下僅僅有Microsoft Visual C++支持64位,但我們累積了非常多Delphi的Library和組件,基于穩(wěn)定度及開發(fā)時效的考慮,并不希望換開發(fā)環(huán)境,眼下做法是花非常多力氣和C++整合。……我今天就是來問Delphi什么時候支持64Bit?」
CodeGear臺灣區(qū)產(chǎn)品經(jīng)理李維介紹完新產(chǎn)品,聽眾迫不及待的走向講臺,紛紛問起新產(chǎn)品的兼容問題,知網(wǎng)生物識別科技技術(shù)長江元麟也是當(dāng)中一位,他特地來問一個問題,由于這將影響到公司未來產(chǎn)品的開發(fā)效率。
Windows Vista出現(xiàn)帶來64位新挑戰(zhàn)。知網(wǎng)生物識別科技去年面臨客戶要求在Vista的Content Menu技術(shù)上支持64位檔案指紋加密。江元麟表示:「眼下僅僅有Microsoft Visual C++支持64位,但我們累積了非常多Delphi的Library和組件,基于穩(wěn)定度及開發(fā)時效的考慮,并不希望換開發(fā)環(huán)境,眼下做法是花非常多力氣和C++整合。……我今天就是來問Delphi什么時候支持64Bit?」
從1995年發(fā)表1.0版后,12年來,Delphi歷經(jīng)11個版本號更迭,從16位的1.0到.NET平臺的BDS 2006。開發(fā)部門獨(dú)立成立CodeGear 后,又回到原生Win32環(huán)境下的Delphi 2007 for Win32。江元麟24年程序開發(fā)經(jīng)驗,一路見證了Delphi的變化。
從1987年開始接觸Borland,江元麟用過Turbo Pascal和Turbo C。1995年,由于工作須要開始使用Delphi。2000年,他投入生物識別產(chǎn)業(yè),繼續(xù)使用Delphi 5開發(fā),他指出:「Delphi有一個非常好的長處是能夠開發(fā)自己的組件,它的組件讓我們的產(chǎn)品開發(fā)加速非常快。新進(jìn)project師能立即就作一些簡單的開發(fā),這是Delphi最優(yōu)秀的地方。」
相較于當(dāng)時其它開發(fā)工具,他覺得:「VB當(dāng)時沒辦法全然用對象導(dǎo)向的方式去開發(fā)組件,比較不是給Engineer用,而是給Power User使用。而C++要客制化組件難度頗高,它的平臺沒有那么靈活。」
為何一直用Delphi?江元麟解釋說:「是由于它的平臺,非常多Source Code都有釋出,所以你能夠開發(fā)一些真的是自己會用到的基層組件。我們公司的組件已經(jīng)累積5年到10年都有,一個組件能夠撐那么久,代表它非常穩(wěn)定了,相對的我們公司的產(chǎn)品出來質(zhì)量是非常好的,這也是Delphi的貢獻(xiàn)……這也是為什么,我們寧可在Delphi上花力氣結(jié)合C++來處理新挑戰(zhàn)。」3年前,知網(wǎng)的識別軟件能讓Pentium 4 處理器在1秒內(nèi)辨識十萬枚指紋,是當(dāng)時國外最高速度的3倍,他說:「這當(dāng)中有一部份是由Delphi編譯出來的程序代碼效率相當(dāng)好的貢獻(xiàn)。」
盡管當(dāng)天江元麟的問題沒有立即的解決方式,但對于脫離Borland后的GodeGear,他表示:「蠻喜歡分割出來的CodeGear,曾經(jīng)步調(diào)非常慢,如今步調(diào)非常快,我比較喜歡,聽李維傳遞的訊息,感覺比較有活力,但希望能維持曾經(jīng)的速度和質(zhì)量。兩年前看到Borland公司非常亂,覺得非常遺憾,周圍的人兩年前已經(jīng)慢慢轉(zhuǎn)到C#去了。」,他接著說:「我們本來去年要考慮轉(zhuǎn)成C#,如今要又一次考慮了
編碼的藝術(shù)
baidu
我在本文中要談的不是編碼的技術(shù)實現(xiàn),我所關(guān)注的是關(guān)于編碼的風(fēng)格的問題。我在編寫了e速的編碼規(guī)范之后,產(chǎn)生了要寫一些關(guān)于程序編碼風(fēng)格的念頭;因此,就有了以下的文章,這些僅僅是本人的想法,可能在文章中還有一些未盡如人意的地方,所以肯請大家能夠給與諒解。
非常多人在談到編碼的藝術(shù)時,總會說我的程序怎么怎么的厲害,功能多么的強(qiáng)大,好像什么事情都能完畢一樣;可是去運(yùn)行他的程序,bug不斷;連他自己都不知道錯在了什么地方。打開他的程序一看,代碼寫的凌亂不堪;命名上不規(guī)范,為了偷懶和簡便有些命名干脆就用一個字母或者其它的簡單符號取代,甚至于有些代碼連他自己也搞不清是干什么了,更不要說怎樣讓別人去改動了….本人編碼也快4個年頭了,像上述的樣例遇見過不少,整個程序改動起來實在是頭疼。
的確,一件好的藝術(shù)品不在于其功能是多么的完好,而在于別人贊賞起來是否有它內(nèi)在的美和是否非常easy就把它從雜貨堆里一眼就能辨認(rèn)出來;畢竟它是藝術(shù)品而非日用品。我們敲代碼也是同樣,假設(shè)程序中的格式非常隨意,比如對數(shù)組做循環(huán),一會兒採用下標(biāo)變量從下到上的方式,一會兒又用從上到下的方式;對字符串一會兒用s t r c p y做復(fù)制,一會兒又用f o r循環(huán)做復(fù)制;等等。這些變化就會使人非常難看清實際上究竟是怎么回事了。
寫好一個程序,當(dāng)然須要使它符合語法規(guī)則、修正當(dāng)中的錯誤和使它運(yùn)行得足夠快,可是實際應(yīng)該做的遠(yuǎn)比這多得多。程序不僅須要給計算機(jī)讀,也要給程序猿讀。一個寫得好的程序比那些寫得差的程序更easy讀、更easy改動。經(jīng)過了怎樣寫好程序的訓(xùn)練,生產(chǎn)的代碼更可能是正確的。
凝視:凝視是幫助程序讀者的一種手段。可是,假設(shè)在凝視中僅僅說明代碼本身已經(jīng)講明的事情,或者與代碼矛盾,或是以精心編排的形式干擾讀者,那么它們就是幫了倒忙。最好的凝視是簡潔地點明程序的突出特征,或是提供一種概觀,幫助別人理解程序。在標(biāo)注凝視的同一時候,應(yīng)該注意以下的問題:
不要大談明顯的東西。凝視不要去說明明確白的事,比方i + +能夠?qū)值加1等等。凝視應(yīng)該提供那些不能一下子從代碼中看到的東西,或者把那些散布在很多代碼里的信息收集到一起。當(dāng)某些難以捉摸的事情出現(xiàn)時,凝視能夠幫助澄清情況。假設(shè)操作本身非常明了,反復(fù)談?wù)撍鼈兙褪钱嬌咛碜懔?#xff1b;給函數(shù)和全局?jǐn)?shù)據(jù)加凝視。凝視當(dāng)然能夠有價值。對于函數(shù)、全局變量、常數(shù)定義、結(jié)構(gòu)和類的域等,以及不論什么其它加上簡短說明就能夠幫助理解的內(nèi)容,我們都應(yīng)該為之提供凝視。全局變量常被分散使用在整個程序中的各個地方,寫一個凝視能夠幫人記住它的意義,也能夠作為參考。放在每個函數(shù)前面的凝視能夠成為幫人讀懂程序的臺階。假設(shè)函數(shù)代碼不太長,在這里寫一行凝視就足夠了。有些代碼原本非常復(fù)雜,可能是由于算法本身非常復(fù)雜,或者是由于數(shù)據(jù)結(jié)構(gòu)非常復(fù)雜。在這些情況下,用一段凝視指明有關(guān)文獻(xiàn)對讀者也非常有幫助。此外,說明做出某種決定的理由也非常有價值。
職業(yè)程序猿也常被要求凝視他們的全部代碼。可是,應(yīng)該看到,盲目遵守這些規(guī)則的結(jié)果卻可能是丟掉了凝視的真諦。凝視是一種工具,它的作用就是幫助讀者理解程序中的某些部分,而這些部分的意義不easy通過代碼本身直接看到。我們應(yīng)該盡可能地把代碼寫得easy理解。在這方面你做得越好,須要寫的凝視就越少。好的代碼須要的凝視遠(yuǎn)遠(yuǎn)少于差的代碼。
編碼的風(fēng)格:全局變量應(yīng)該採用具有描寫敘述意義的名字,局部變量用短名字。函數(shù)採用動作性的名字。給神奇的數(shù)起個名字。現(xiàn)實中存在很多命名約定或者本地習(xí)慣。常見的比方:指針採用以p結(jié)尾的變量名,比如n o d e p;全局變量用大寫開頭的變量名,比如G l o b a l;常量用全然由大寫字母拼寫的變量名,如C O N S T A N T S等。命名約定能使自己的代碼更easy理解,對別人寫的代碼也是一樣。這些約定也使人在寫代碼時更easy決定事物的命名。對于長的程序,選擇那些好的、具有說明性的、系統(tǒng)化的名字就更加重要。
保持一致性。要準(zhǔn)確。以縮行形式顯示程序結(jié)構(gòu)。使用表達(dá)式的自然形式。利用括號排除歧義。分解復(fù)雜的表達(dá)式。要清晰。當(dāng)心副作用。使用一致的縮行和加括號風(fēng)格。為了一致性,使用習(xí)慣使用方法。用else-if 處理多路選擇。避免使用函數(shù)宏。給宏的體和參數(shù)都加上括號。這些都是非常瑣碎的事情,但卻又是非常有價值的,就像保持書桌整潔能使你easy找到東西一樣。與你的書桌不同的是,你的程序代碼非常可能還會被別人使用。
用縮行顯示程序的結(jié)構(gòu)。採用一種一致的縮行風(fēng)格,是使程序呈現(xiàn)出結(jié)構(gòu)清晰的最省力的方法。
用加括號的方式排除二義性。括號表示分組,即使有時并不必要,加了括號也可能把意圖表示得更清晰。在混合使用互相無關(guān)的運(yùn)算符時,多寫幾個括號是個好主意。C語言以及與之相關(guān)的語言存在非常險惡的優(yōu)先級問題,在這里非常easy犯錯誤。比如,由于邏輯運(yùn)算符的約束力比賦值運(yùn)算符強(qiáng),在大部分混合使用它們的表達(dá)式中,括號都是必需的。
利用語言去計算對象的大小。不要大談明顯的東西。給函數(shù)和全局?jǐn)?shù)據(jù)加凝視。不要凝視不好的代碼,應(yīng)該重寫。不要與代碼矛盾。澄清情況,不要添亂。
界面的風(fēng)格:隱蔽實現(xiàn)的細(xì)節(jié)。不要在用戶背后搞小動作。在各處都用同樣方式做同樣的事。釋放資源與分配資源應(yīng)該在同一層次進(jìn)行。在低層檢查錯誤,在高層處理。僅僅把異經(jīng)常使用在異常的情況。
寫良好的代碼更easy閱讀和理解,差點兒能夠保證當(dāng)中的錯誤更少。進(jìn)一步說,它們通常比那些馬馬虎虎地堆起來的、沒有細(xì)致推敲過的代碼更短小。在這個拼命要把代碼送出門、去趕上最后期限的時代,人們非常easy把風(fēng)格丟在一旁,讓將來去管它們吧。可是,這非常可能是一個代價非常昂貴的決定。上面的一些陳述性的言語充分的說明了,假設(shè)對好風(fēng)格問題重視不夠,程序中哪些方面可能出毛病。草率的代碼是非常壞的代碼,它不僅難看、難讀,并且經(jīng)常崩潰。好風(fēng)格應(yīng)該成為一種習(xí)慣。假設(shè)你在開始寫代碼時就關(guān)心風(fēng)格問題,假設(shè)你花時間去審視和改進(jìn)它,你將會逐漸養(yǎng)成一種好的編程習(xí)慣。一旦這樣的習(xí)慣變成自己主動的東西,你的潛意識就會幫你照料很多細(xì)節(jié)問題,甚至你在工作壓力下寫出的代碼也會更好
Delphi面向?qū)ο蟮木幊谭椒?/p>
baidu
Delphi的編程語言是以Pascal為基礎(chǔ)的。Pascal語言具有可讀性好、編寫easy的特點,這使得它非常適合作為基礎(chǔ)的開發(fā)語言。同一時候,使用編譯器創(chuàng)建的應(yīng)用程序僅僅生成單個可運(yùn)行文件(.EXE),正是這樣的結(jié)合,使得Pascal成為Delphi這樣的先進(jìn)開發(fā)環(huán)境的編程語言。
本章中,我們將討論Object Pascal的主要特點,并解說怎樣在事件處理過程和其它應(yīng)用程序中,使用它來編制程序代碼。本章將解說Delphi應(yīng)用程序中最經(jīng)常使用的Object Pascal語法,而不是Pascal語言的一切細(xì)節(jié)。假設(shè)您全然不熟悉Pascal編程,請參閱一些基礎(chǔ)的Pascal教程。假設(shè)您具有編程經(jīng)驗,并能熟練地使用其它流行程序語言,您將在本章的Object Pascal中發(fā)現(xiàn)一些同樣的概念。假設(shè)您已經(jīng)熟悉了Borland Pascal,就能夠高速瀏覽或跳過本章。
2.1 編寫Object Pascal程序代碼
在前邊的章節(jié)中,我們通過例程,已經(jīng)編寫了幾行簡單的代碼。在本章中,我們將從熟悉Pascal編程的角度,配合實例,解說Object Pascal編程的基本方法。
在編寫自己的Object Pascal程序時,要注意程序的可讀性。Pascal語言是英式結(jié)構(gòu)語言,在程序中選擇合適的縮排、大寫和小寫風(fēng)格,并在須要時將程序代碼分行,會使得程序代碼能夠非常easy地被自己和他人讀懂。一般的程序猿都有這樣的體驗:假設(shè)不給程序加上適當(dāng)?shù)淖⒔?#xff0c;一段時間后,自己也難以理清程序的流程。給程序及時地加上凝視是良好的編程習(xí)慣。Delphi的凝視須要加注在{}之間,編輯器會把它們處理成為空白。Delphi保留了Borland Pascal編輯器的風(fēng)格,keyword採用黑體字,被凝視的部分會變暗,這使得編程風(fēng)格良好,易讀易寫。
2.1.1 編寫賦值語句
在事件處理過程中,最經(jīng)常使用到的工作就是把一個新值賦給一個屬性或變量。在設(shè)計用戶界面時,能夠使用Object Inspector(Object Inspector)來改變其屬性;但有時須要在程序運(yùn)行時改變屬性的值,并且有些屬性僅僅能在運(yùn)行時改變,這些屬性在Delphi的在線幫助的“Proprety”主題中被標(biāo)為運(yùn)行期屬性。進(jìn)行這樣的改變,就必須使用賦值語句。
下文的賦值語句表征一個OnClick事件。當(dāng)button按動后,將編輯框部件Edit1的Color屬性置為clRed:
procedure TForm1.Button1Click(Sender: TObject);
begin
Edit1.Color := clRed;
end;
當(dāng)按動button后賦值語句被運(yùn)行,編輯框變成紅色。
在語句中,部件的名稱在屬性前,中間用“.”表示屬性的所屬關(guān)系。這樣就準(zhǔn)確地指定了要將clRed值賦給哪一部件的哪一屬性。賦值號為“:=”,不論給屬性還是給變量賦值,都是將右邊的值賦給左邊的屬性或變量。
當(dāng)將一個屬性值、變量、常量或文本數(shù)據(jù)賦給屬性或變量時,所賦值的類型和接受此值的屬性或變量的類型應(yīng)同樣或兼容。一個屬性或變量的類型定義了此屬性或變量的可能值集合,也定義了程序代碼能夠運(yùn)行的運(yùn)算。在前邊的例程中,編輯框部件的Color屬性和clRed的類型都是TColor。能夠在在線幫助中找到一個屬性的類型;第二種方法是在Object Inspector中選定該屬性值段,并按下F1鍵,則類型將在屬性說明的結(jié)尾處列出,比如Color屬性列出下邊的語句:
Property Color : TColor;
有些屬性是僅僅讀(Read Only)的,它們僅僅能被讀取,不能被改變。請查閱在線幫助,在Delphi中這些僅僅讀屬性都有注解。
2.1.2 標(biāo)識符的說明與使用
標(biāo)識符是Delphi應(yīng)用程序中一些量的名稱,這些量包括變量(var)、常量(const)、類型(type)、過程(procedure)、方法(Method)及其它,Object Pascal 在應(yīng)用標(biāo)識符時,必須首先說明它們。Object Pascal是強(qiáng)類型語言,它的編譯器能夠檢查確保賦給變量或?qū)傩缘闹凳钦_的類型,以便于您改正錯誤。由于Object Pascal是編譯語言,所以Delphi的運(yùn)行速度要比使用解釋語言快得多。在使用標(biāo)識符前說明它們,能夠降低程序錯誤并增加代碼的效率。
2.1.2.1 變量
變量是程序代碼中代表一個內(nèi)存地址的標(biāo)識符,而此地址的內(nèi)存內(nèi)容在程序代碼運(yùn)行時能夠被改變。在使用變量前必須對它進(jìn)行說明,即對它進(jìn)行命名,并說明它的類型。在全部變量說明曾經(jīng)加上保留字var。變量說明左邊是變量的名稱,右邊則是該變量的類型,中間用(:)隔開。
var
Value ,Sum : Integer;
Line : String;
在窗口中增加一個名稱為Edit1的編輯框,再增加一個名稱(屬性Name)為Add的button部件,并建立例如以下的事件處理過程:
procedure TForm1.addClick(Sender: TObject);
var
X , Y: Integer;
begin
X := 100;
Y := 20;
Edit1.Text := IntToStr(X + Y);
end;
在本例中,當(dāng)按動ADDbutton時,編輯框中顯示值120。在Object Pascal中,必須確保變量或?qū)傩员毁x予類型同樣或兼容的值。您能夠嘗試將賦給X的值改為100.0,或去掉IntToStr函數(shù),在編譯時會出現(xiàn)類型不匹配的錯誤,這也說明了Object Pascal強(qiáng)類型語言的特點。
2.1.2.2 提前定義類型
Object Pascal有多個提前定義的數(shù)據(jù)類型,您能夠說明不論什么這些類型的變量:
整形:Integer的范圍是-32768到32767,占2字節(jié)的內(nèi)存;Shortint從-128到127,占1字節(jié)內(nèi)存;Longint從-2147443648到2147483647 占4字節(jié)內(nèi)存;Byte從0到255,占1字節(jié);Word從0到65535,占2字節(jié)內(nèi)存。它們都是沒有小數(shù)部分的數(shù)字。
實型:Single能夠包括7到8位有效小數(shù)部分,占用4字節(jié)的內(nèi)存;Double類能夠包括15到16位有效小數(shù)部分,占用8字節(jié)的內(nèi)存;Extended類型包括19到20位有效小數(shù)部分,占用10字節(jié)內(nèi)存;Comp能夠包括19到20位有效小數(shù)部分,占用8字節(jié)內(nèi)存。以上實數(shù)類型僅僅有在8087/80287選項[N+]打開才干夠使用。Real能夠包括11到12位有效小數(shù)部分,占用6字節(jié)內(nèi)存。它僅僅有在和曾經(jīng)Borland Pascal兼容的情況下才使用,否則應(yīng)使用Double或Extended。
布爾型:Boolean,僅僅包括true或False兩個值,占用1字節(jié)內(nèi)存。
字符型:Char,一個ASCII字符;字符串類型String一串最長可達(dá)255個ASCII字符。
指針型:Pointer,能夠指向不論什么特定類型。
字符串型:PChar,是一個指向以零結(jié)尾的字符串的指針。
除了提前定義類型外,Delphi還有自行定義的類型。上述例程的TColor就是這樣的類型。此外,用戶還能夠定義自己的數(shù)據(jù)類型,這部分內(nèi)容將在下文中具體講述。
整型類別和實型類別都各有五種類型,同一類別中,全部的類型與其它同類別的都相容,您能夠?qū)⒁环N類型的值賦給同樣類別中不同類型的變量或?qū)傩?#xff0c;而僅僅須要這個值的范圍在被賦值的變量或?qū)傩缘目赡苤捣秶鷥?nèi)。比如,對于一個Shortint型的變量,能夠接受在-128到127范圍內(nèi)的隨意整數(shù),比如Shortint類型的7;您不能將300賦給它,由于300已經(jīng)超出了Shortint的范圍了。將范圍檢查功能打開(選用Options|Project,并在Compiler Options Page中選擇Range Checking),將會檢查出一個范圍錯誤;假設(shè)Range Checking沒有被打開,那么程序代碼將能夠運(yùn)行,但被賦值的值將不是您期望的值。
在一些情況下,您能夠進(jìn)行不同類型的變量或?qū)傩缘馁x值。一般來說,能夠?qū)⒁粋€較小范圍的值賦給一個較大范圍的值。比如,您能夠?qū)⒄椭?0賦給一個接受實型值的Double屬性而使得值成為10.0,但假設(shè)將一個Double類型的值賦給整形變量,則會出現(xiàn)類型錯誤。假設(shè)您不清晰類型的兼容性,能夠參閱Delphi的在線幫助中“Type Compatibility and Assignment Compatibility”主題。
2.1.2.3 常量
常量在說明時就被賦予了一個值,在程序運(yùn)行過程中是不可改變的。以下的樣例說明了三個常量:
const
Pi = 3.14159;
Answer = 342;
ProductName = "Delphi";
象變量一樣,常量也有類型。不同的是,常量假設(shè)其類型就是常量說明中其所代表的值的類型。上文的三個常量的類型各自是real型、整形、字符串型。常量用“= " 表示兩邊的值是相等的。
2.1.3 過程與函數(shù)
過程與函數(shù)是程序中運(yùn)行特定工作的模塊化部分。Delphi的運(yùn)行庫包括很多過程與函數(shù)以供您的應(yīng)用程序調(diào)用。您不必了解過程與函數(shù)的邏輯,但要知道過程與函數(shù)的用途。在對象中說明的過程和函數(shù)稱為方法(Method)。全部的事件處理過程都是過程,以保留字procedure開頭。每個事件處理過程僅僅包括了當(dāng)這一事件發(fā)生時須要運(yùn)行的程序代碼。在事件處理過程中使用Delphi已經(jīng)存在的過程與函數(shù),僅僅需在程序代碼中調(diào)用它們就可以。
2.1.3.1 一個調(diào)用Delphi方法的簡單例程
下文將通過對一個Memo部件的文本進(jìn)行剪切、拷貝、粘貼、清除等編輯的應(yīng)用程序編制,介紹使用Delphi過程和函數(shù)的調(diào)用方法。
Memo(備注)部件有一個CutToClipboard方法,實現(xiàn)將用戶在memo中選擇的文本移到剪貼板上去。由于這個功能已經(jīng)被建立在此方法中了,所以您僅僅需知道這種方法做什么以及怎樣使用它就可以。
以下的語句表明怎樣調(diào)用一個名為Memo1的memo部件的CutToClipboard方法:
Memo1.CutToClipboard;
通過指定Memo1的名稱,說明調(diào)用哪一個部件的CutToClipboard方法。假設(shè)不指明對象名稱,Delphi會顯示Unknown identifier錯誤。當(dāng)該事件處理過程被觸發(fā),程序會運(yùn)行CutToclipboard中的語句,將Memo1中的文本剪貼到剪貼板上去。
下文的例程展示了怎樣調(diào)用Delphi的方法,實現(xiàn)將備注部件的文本信息剪切、復(fù)制到剪貼板上;將剪貼板上的標(biāo)記文本粘貼到備注中,清除備注部件中的全部文本等四個功能。
打開一個新的空窗口,增加一個memo部件和四個button,并排列整齊。改變button部件的Name屬性,分別命名為Cut,Copy,Paste,Clear。您會發(fā)現(xiàn),當(dāng)Name屬性發(fā)生改變時,Caption屬性將發(fā)生對應(yīng)的變化。在Caption屬性前加標(biāo)“&”號設(shè)立加速鍵
將memo部件的ScrollBars屬性設(shè)為ScVertical,以便加上滾行條。將WordWrap屬性設(shè)置為True,這樣當(dāng)用戶輸入文本到達(dá)Memo部件的右邊緣時會自己主動回行。將Line屬性第一行的Memo1文本刪除,使得memo部件在初始顯示時為空的。
為每個button建立例如以下的事件處理過程:
procedure TForm1.CutClick(Sender: TObject);
begin
Memo1.CutToClipboard;
end;
procedure TForm1.CopyClick(Sender: TObject);
begin
Memo1.CopyToClipboard;
end;
procedure TForm1.PasteClick(Sender: TObject);
begin
Memo1.PasteFromClipboard;
end;
procedure TForm1.ClearClick(Sender: TObject);
begin
Memo1.clear;
end;
運(yùn)行此程序。您能夠在備注部件中輸入文本,在進(jìn)行了文本的標(biāo)記后,能夠隨意地進(jìn)行剪切、拷貝、粘貼和清除。當(dāng)button被按動時,就調(diào)用對應(yīng)的過程進(jìn)行處理。用戶能夠通過查閱在線幫助進(jìn)行Memo部件的Topic Search,在Memo Component項中查閱Method,會得到以上過程的具體說明。
2.1.3.2 調(diào)用Delphi的含參過程
有些過程要求用戶指明參數(shù)。被調(diào)用的過程會在運(yùn)行時使用傳入的參數(shù)值,這些值在過程中被覺得是已經(jīng)被說明的變量。比如,LoadFromFile方法在TString對象中被說明為:
Procedure LoadFromFile(const FileName: String);
在調(diào)用這一過程時,應(yīng)指明FileName參數(shù)是要裝入的文件名。以下的程序?qū)⑾却蜷_Open對話框,當(dāng)您選擇了一個文件后,Delphi將把該文件讀入一個Memo部件:
begin
OpenDialog.Execute;
Memo1.lines.LoadFromFile(OpenDialog.FileName);
end;
2.1.3.3 使用Delphi函數(shù)
與過程一樣,函數(shù)的程序代碼也運(yùn)行特定的工作。它和過程的差別為:函數(shù)運(yùn)行時會返回一個值,而過程則沒有返回值。函數(shù)能夠用來賦給一個屬性或變量;也能夠使用返回值來決定程序的流程。
前文中我們實際上已經(jīng)接觸過了函數(shù)。在講述變量時,曾用到過以下的程序段: Edit1.Text := IntToStr(X + Y);當(dāng)中,IntToStr(Value)把一個LongInt類型的數(shù)值轉(zhuǎn)化為字符串的值,Value是IntToStr唯一的參數(shù),它能夠是一個整形的值、變量、屬性或產(chǎn)生整形值的表達(dá)式。調(diào)用函數(shù),必須把返回值賦給和此返回值類型兼容的變量或?qū)傩浴?/p>
有些函數(shù)返回一個True或False的布爾量,用戶的程序能夠依據(jù)返回值來決定跳轉(zhuǎn)。下文的例程講述了函數(shù)返回值為Boolean的推斷使用方法:
在窗口中增加一個ColorDialog對象和一個Name屬性為ChangeColor的button。為button的OnClick事件建立事件處理步驟例如以下:
procedure TForm1.ChangeColorClick(Sender: TObject);
begin
if ColorDialog1.Execute then
Form1.Color := ColorDialog1.Color
else
Form1.Color := clRed;
end;
此事件處理過程使用一個返回Boolean值的Execute方法。按動button,并在顏色對話框中選擇一個顏色。假設(shè)按動OKbutton,ColorDialog.Execute方法將返回True,則Form1.Color將被賦值為ColorDialog1.Color,窗口顯現(xiàn)您選用的顏色;假設(shè)按動顏色對話框的Cancelbutton,方法將返回False值,窗口將變?yōu)榧t色。
2.1.4 跳轉(zhuǎn)語句
Object Pascal的跳轉(zhuǎn)語句有if和case兩個。
2.1.4.1 if語句
if語句會計算一個表達(dá)式,并依據(jù)計算結(jié)果決定程序流程。在上文的例程中,依據(jù)ColorDialog.Execute的返回值,決定窗口的背景顏色。if保留字后尾隨一個生成Boolean值True或False的表達(dá)式。一般用“=”作為關(guān)系運(yùn)算符,比較產(chǎn)生一個布爾型值。當(dāng)表達(dá)式為True時,運(yùn)行then后的語句。否則運(yùn)行else后的代碼,if語句也能夠不含else部分,表達(dá)式為False時自己主動跳到下一行程序。
if語句能夠嵌套,當(dāng)使用復(fù)合語句表達(dá)時,復(fù)合語句前后需加上begin…end。else保留字前不能加“;”,并且,編譯器會將else語句視為屬于最靠近的if語句。必要時,須使用begin…end保留字來強(qiáng)迫else部分屬于某一級的if語句。
2.1.4.2 case語句
case語句適用于被推斷的變量或?qū)傩允钦巍⒆址汀⒚杜e型或子界型時(LongInt除外)。用case語句進(jìn)行邏輯跳轉(zhuǎn)比編寫復(fù)雜的if語句easy閱讀,并且程序代碼整形較快。
以下的例程顯示一個使用case語句的窗口:
建立例如以下的事件處理過程:
procedure TForm1.Button1Click(Sender: TObject);
var
Number : Integer;
begin
Number := StrToInt(Edit1.Text);
case Number of
1,3,5,7,9: Label2.Caption := '奇數(shù)';
0,2,4,6,8: Label2.Caption := '偶數(shù)';
10..100:
begin
Label2.Caption := '在10到100之間';
Form1.Color := clBlue;
end;
else
Label2.Caption := '大于100或為負(fù)數(shù)';
end;
end;
運(yùn)行程序,當(dāng)Edit1部件接受到一個值,并按動“OK”button觸發(fā)程序后,Number便被賦值為用戶輸入的數(shù)值。case語句依據(jù)Number的值推斷該運(yùn)行哪一條語句。象if語句一樣。case語句也有可選擇的else部分。case語句以end結(jié)尾。
2.1.5 循環(huán)語句
Object Pascal的循環(huán)語句有三種:repeat、while和for語句。
2.1.5.1 repeat語句
repeat語句會反復(fù)運(yùn)行一行或一段語句直到某一狀態(tài)為真。語句以repeat開始,以until結(jié)束,其后尾隨被推斷的布爾表達(dá)式。參閱以下的例程:
i := 0;
repeat
i := i+1;
Writen(i);
until i=10;
當(dāng)此語句被運(yùn)行時,窗口的下方會出現(xiàn)1到10的數(shù)字。布爾表達(dá)式 i=10 (注意,與其它語言不同的是,“=”是關(guān)系運(yùn)算符,而不能進(jìn)行賦值操作)直到repeat..until程序段的結(jié)尾才會被計算,這意味著repeat語句至少會被運(yùn)行一次。
2.1.5.2 while語句
while語句和repeat語句的不同之處是,它的布爾表達(dá)式在循環(huán)的開頭進(jìn)行推斷。while保留字后面必須跟一個布爾表達(dá)式。假設(shè)該表達(dá)式的結(jié)果為真,循環(huán)被運(yùn)行,否則會退出循環(huán),運(yùn)行while語句后面的程序。
以下的例程達(dá)到和上面的repeat例程達(dá)到同樣的效果:
i := 0;
while i<10 do
begin
i := i+1;
writeln(i);
end;
2.1.5.3 for語句
for語句的程序代碼會運(yùn)行一定的次數(shù)。它須要一個循環(huán)變量來控制循環(huán)次數(shù)。您須要說明一個變量,它的類型能夠是整形、布爾型、字符型、枚舉型或子界型。
以下的程序段會顯示1到5的數(shù)字,i為控制變量:
var
i : integer;
for i := 1 to 5 do
writeln(i);
以上介紹了三種循環(huán)語句。假設(shè)您知道循環(huán)要運(yùn)行多少次的話,能夠使用for語句。for循環(huán)運(yùn)行速度快,效率比較高。假設(shè)您不知道循環(huán)要運(yùn)行多少次,但至少會運(yùn)行一次的話,選用repeat..until語句比較合適;當(dāng)您覺得程序可能一次都不運(yùn)行的話,最好選用while..do語句。
2.1.6 程序模塊
程序模塊在Object Pascal中是非常重要的概念。它們提供了應(yīng)用程序的結(jié)構(gòu),決定了變量、屬性值的范圍及程序運(yùn)行的過程。它由兩個部分組成:可選擇的說明部分和語句部分。假設(shè)有說明部分,則必在語句部分之前。說明部分包括變量說明、常量說明、類型說明、標(biāo)號說明、程序,函數(shù),方法的說明等。語句部分?jǐn)⑹隽丝蛇\(yùn)行的邏輯行動。
在Delphi中,最常見的程序模塊便是事件處理過程中的程序模塊。以下的事件處理過程是含有變量說明部分的程序模塊:
procedure TForm.Button1Click(Sender Tobject);
var {程序模塊的說明部分}
Name : string;
begin {程序模塊的語句部分}
Name := Edit1.Text;
Edit2.Text := 'Welcome to Delphi'+Name;
end; {程序模塊結(jié)束}
庫單元也是程序模塊。庫單元的interface部分含有庫函數(shù)、類型、私有,公有域的說明,也能夠含有常量、變量的說明。這一部分能夠作為程序模塊的說明部分。在庫單元的implementation部分中通常含有各種事件處理過程,它們能夠視為模塊的語句部分,是事件處理模塊。庫單元模塊結(jié)束于庫單元結(jié)束的end.處。
程序模塊中能夠包括其它的程序模塊。上文庫單元模塊中含有事件處理模塊。而庫單元模塊實際是在project程序模塊中。
全部的Delphi應(yīng)用程序都有同樣的基本結(jié)構(gòu)。當(dāng)程序逐漸復(fù)雜時,在程序中增加模塊就可以。比如在庫單元模塊中增加事件處理模塊,向project中增加庫單元模塊等。模塊化編程使得程序結(jié)構(gòu)良好,并且對數(shù)據(jù)具有保護(hù)作用。
2.1.7 關(guān)于作用范圍
2.1.7.1 標(biāo)識符的作用范圍
一個變量、常量、方法、類型或其它標(biāo)識符的范圍定義了這個標(biāo)識符的活動區(qū)域。對于說明這個標(biāo)識符的最小程序模塊而言,此標(biāo)識符是局部的。當(dāng)您的應(yīng)用程序在說明一個標(biāo)識符的程序模塊外運(yùn)行時,該標(biāo)識符就不在此范圍內(nèi)。這意味著此時運(yùn)行的程序無法訪問這個標(biāo)識符,僅僅有當(dāng)程序再度進(jìn)入說明這個標(biāo)識符的程序模塊時,才干夠訪問它。
以下的示意圖表示一個含有兩個庫單元的project,每個庫單元中又各有三個過程或事件處理過程。
2.1.7.2 訪問其它程序模塊中的說明
您能夠在當(dāng)前的程序模塊中訪問其它程序模塊中的說明。比如您在庫單元中編寫一個事件處理過程來計算利率,則其它的庫單元能夠訪問這個事件處理過程。要訪問不在當(dāng)前庫單元中的說明,應(yīng)在這個說明之前加上其它應(yīng)用程序的名稱和一個點號(.)。比如,在庫單元Unit1中有事件處理過程CalculateInterest過程,如今您想在庫單元Unit2中調(diào)用這一過程,則能夠在Unit2的uses子句中增加Unit1,并使用以下的說明:
Unit1.CalculateInterest(PrincipalInterestRate : Double);
應(yīng)用程序的代碼不能在一個模塊外訪問它說明的變量。事實上,當(dāng)程序運(yùn)行跳出一個模塊后,這些變量就不存在于內(nèi)存中了。這一點對于不論什么標(biāo)識符都是一樣的,無論事件處理過程、過程、函數(shù)還是方法,都具有這一性質(zhì)。這樣的標(biāo)識符稱為局部變量。
2.1.7.3 依照作用范圍說明標(biāo)識符
您能夠在應(yīng)用程序的不同地方說明一個標(biāo)識符,而僅僅需保證它們的有效范圍不同就可以。編譯器會自己主動訪問最靠近當(dāng)前范圍的標(biāo)識符。
庫單元的全局變量一般能夠說明在保留字implementation后面。比如,以下的例程實現(xiàn)將兩個編輯框中的整數(shù)相加,顯示在第三個編輯框中。用到了一個整形的全局變量Count:
…implememntation
var
Count : Integer;
procedure TForm1.AddClick(Sender:TObject);
var
FirstNumber,SecondNumber:Integer;
begin
Count := Count + 1;
Counter.Text := IntToStr(Count);
FirstNumber := StrToInt(Edit1.Text);
SecondNumber := StrToInt(Edit2.Text);
Edit3.Text := IntToStr(FirstNumber+SecondNumber);
end;
…
為了實現(xiàn)每按動一次buttonCount增加一次,必須對全程變量Count進(jìn)行初始化處理。在程序庫單元的結(jié)尾處,最后一個end.保留字之前,增加保留字initialization和初始化Count的代碼:
…
initialization
Count := 0;
這樣當(dāng)事件處理過程AddClick被觸發(fā)時,Count就會被增加一次,以表征計算次數(shù)。假設(shè)用面向?qū)ο缶幊?#xff0c;則Count能夠說明成窗口的一個域,這在下一節(jié)中將有講述。
2.1.8 編寫一個過程或函數(shù)
在您開發(fā)Delphi應(yīng)用程序時,所需的大部分代碼都編寫在事件處理過程中,但有時仍然須要編寫不是事件處理過程的函數(shù)或過程。比如,您能夠把在多個事件處理過程中用得到語句編寫成過程,然后不論什么事件處理過程、過程、函數(shù)都能夠象調(diào)用已經(jīng)存在的過程或函數(shù)一樣直接調(diào)用它。長處是您僅僅需編寫一次代碼,并且程序代碼會比較清晰。
2.1.8.1 一個自行編寫的函數(shù)例程
在上文兩個數(shù)相加的程序中,假設(shè)編輯框中無值,則會使得程序出錯中斷。為避免這樣的情況,編寫以下的函數(shù),檢查編輯框中是否有值,如無值,則提醒用戶輸入:
function NoValue(AnEditBox:TEdit):Boolean;
begin
if AnEditBox.Text='' then
begin
AnEditBox.Color := clRed;
AnEditBox.Text := '請輸入整數(shù)值';
Result := True;
end
else
begin
AnEditBox.Color := clWindow;
Result := False;
end;
end;
NoValue函數(shù)會檢查編輯框是否為空,假設(shè)是,編輯框顏色變紅,并提醒用戶輸入一個整數(shù),然后函數(shù)返回真值;Result保留字在Delphi中用來專指函數(shù)返回值。在上文的例程中增加NoValue函數(shù):
procedure TForm1.AddClick(Sender: TObject);
var
FirstNumber,SecondNumber : Integer;
begin
if NoValue(Edit1)or NoValue(Edit2) then
exit;
Count := Count + 1;
Counter.Text := IntToStr(Count);
FirstNumber := StrToInt(Edit1.Text);
SecondNumber := StrToInt(Edit2.Text);
Edit3.Text := IntToStr(FirstNumber+SecondNumber);
end;
假設(shè)當(dāng)中的不論什么一個返回真值,則表示有編輯框空,會運(yùn)行exit過程,使得當(dāng)前的程序模塊停止運(yùn)行,并使得編輯框出現(xiàn)輸值提示。當(dāng)新值被輸入后,再運(yùn)行程序時,紅色提示被隱去,恢復(fù)正常的計算狀態(tài)。
2.1.8.2 過程和函數(shù)的標(biāo)題
每個過程或函數(shù)都以標(biāo)題開始,當(dāng)中包括過程或函數(shù)的名稱和它使用的參數(shù)。過程以保留字procedure開始,函數(shù)以保留字function開始。參數(shù)位于括號中面,每個參數(shù)以分號分隔。比如:
procedure validateDate(Day : Integer; month : Integer; Year : Integer);
您也能夠?qū)⑼瑯宇愋偷膮?shù)組合在一起,則上述過程頭寫作:
procedure ValidateDate(Day, Month, Year : Integer);
函數(shù)在標(biāo)題中還多了一項:返回值的類型。以下是一個返回值為Double型的函數(shù)標(biāo)題:
function CalculateInterest(principal,InterestRate:Double):Double;
2.1.8.3 函數(shù)和過程中的類型說明
一個過程或函數(shù)程序模塊也含有說明部分和語句部分。說明部分能夠包括類型說明、變量說明、常量說明等。除了Object Pascal語言中已經(jīng)定義的類型之外,Delphi的應(yīng)用程序還能夠建立新的數(shù)據(jù)類型。類型說明部分有保留字type開始。以下是一些類型的說明:
type
Tcount = Integer;
TPrimaryColor = (Red,Yellow,Blue);
TTestIndex = 1..100;
TTextValue = -99..99;
TTestList = array [TTestIndex] of TTestValue;
TCharVal = Ord('A')..Ord('Z') ;
Today = (Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,
Sunday) ;
在類型標(biāo)識符后面,用“=”號定義了新的類型。類型界定了變量的取值范圍,比如,TCount類型的變量必須是整形值;一個TPrimaryColor類型的變量僅僅能是red、yellow或blue等等。每個類型的名稱都是由字母T開始,這并不是必須的,但它是Delphi的慣例,在差別類型名和標(biāo)識符時非常實用。類型說明能夠是局部的,也能夠是全局的。假設(shè)您把它放在implementation后面,則表明對于庫單元來講,它是全局的,全部的事件處理過程和其它的過程、函數(shù)都能夠調(diào)用它。假設(shè)類型是在過程中被說明的,則是局部的,離開這一過程,該類型將失效。
一般來講,在過程和函數(shù)中,不論什么類型說明都在變量說明之前,而不論什么變量說明都在常量之前。可是,僅僅要遵從說明必須在過程與函數(shù)的標(biāo)題之后,并且在程序代碼之前,即是有效的。
2.1.8.4 過程和函數(shù)的語句部分
過程或函數(shù)的語句部分由begin開始,end結(jié)束。函數(shù)須要一個返回值。能夠?qū)⒎祷刂蒂x給函數(shù)名稱,也能夠?qū)⒎祷刂蒂x給Result變量。以下的例程將返回值賦給函數(shù)名稱:
function CalculateInterest(Principal,InterestRate: Double):Double;
begin
CalculateInterest := Principal * InterestRate;
end;
將返回值賦給Result變量也是能夠的,則上面的程序改為:
Result := Principal*InterestRate;
以下是這個函數(shù)的調(diào)用方法:
InterestEarned :=CalculateInterest(2000,0.012);
在Implementation后面的過程和函數(shù),能夠且僅僅能被此庫單元的事件處理過程使用。要讓過程和函數(shù)能夠被其它的程序庫單元使用,則須要將過程或函數(shù)的標(biāo)題部分放在庫單元中的interface部分,而把含標(biāo)題的整個過程或函數(shù)放在庫單元的inplementation部分,并在要訪問這個過程或函數(shù)的庫單元的uses子句中增加說明這個過程或函數(shù)的庫單元名稱。
2.1.8.5 函數(shù)的遞歸調(diào)用
在Object Pascal中,過程或函數(shù)必須先說明再調(diào)用。上文的NoValue函數(shù)必須在使用它的事件處理過程之前說明和運(yùn)行,否則程序會報告一個未知標(biāo)識符的錯誤。
以上規(guī)則在遞歸調(diào)用時是例外情況。所謂遞歸調(diào)用,是指函數(shù)A調(diào)用函數(shù)B,而函數(shù)B又調(diào)用函數(shù)A的情況。在遞歸調(diào)用中,函數(shù)要進(jìn)行前置,即在函數(shù)或過程的標(biāo)題部分最后加上保留字forword。下文的例程是一個遞歸調(diào)用的典型樣例:
…
implementation
var
alpha:Integer;
procedure Test2(var A:Integer):forword;
{Test2被說明為前置過程}
procedure Test1(var A:Integer);
begin
A :=A-1;
if A>0 then
test2(A); {經(jīng)前置說明,調(diào)用未運(yùn)行的過程Test2}
writeln(A);
end;
procedure Test2(var A:Integer);{經(jīng)前置說明的Test2的運(yùn)行部分}
begin
A :=A div 2;
if A>0 rhen
test1(A); {在Test2中調(diào)用已運(yùn)行的過程Test1}
end;
procedure TForm1.Button1Click(Sender:TObject);
begin
Alpha := 15; {給Alpha賦初值}
Test1(Alpha); { 第一次調(diào)用Test1,遞歸開始}
end;
button的OnClick事件處理過程給Alpha賦初值,并實現(xiàn)先減1再除2的循環(huán)遞歸調(diào)用,直到Alpha小于0為止。
2.1.8.6 過程和函數(shù)的參數(shù)
當(dāng)您的程序代碼在調(diào)用一個過程或函數(shù)時,通經(jīng)常使用參數(shù)傳遞數(shù)據(jù)到被調(diào)用的過程或函數(shù)中。最經(jīng)常使用的參數(shù)有數(shù)值參數(shù)、變量參數(shù)和常量參數(shù)三種。
由被調(diào)用過程或函數(shù)定義的參數(shù)為形參,而由調(diào)用過程或函數(shù)指明的參數(shù)叫實參。在NoValue函數(shù)中,說明函數(shù)體中的AnEditBox是形參,而調(diào)用時在if NoValue(Edit1)…中,Edit1是實參。
數(shù)值參數(shù)在運(yùn)行過程中僅僅改變其形參的值,不改變事實上參的值,即參數(shù)的值不能傳遞到過程的外面。試看以下的例程:
procedure Calculate(CalNo:Integer);
begin
CalNo := CalNo*10;
end;
用以下例程調(diào)用Calculate函數(shù):
…
Number := StrToInt(Edit1.Text);
Calculate(Number);
Edit2.Text := IntToStr(Number);
…
Number接受由編輯框1輸入的數(shù)值,經(jīng)Calculate過程運(yùn)算。它是一個數(shù)值型實參。在進(jìn)入Calculate函數(shù)后,會把Number實參拷貝給形參CalNo,在過程中CalNo增大十倍,但并未傳遞出來,因此Number值并未改變,在編輯框2中顯示仍然是編輯框1中的輸入值。形參和實參占用不同的內(nèi)存地址,在過程或函數(shù)被調(diào)用時,將實參的值復(fù)制到形參占用的內(nèi)存中。因此出了過程或函數(shù)后,形參和實參的數(shù)值是不同的,但實參的值并不發(fā)生變化。
假設(shè)您想改變傳入的參數(shù)值,就須要使用變量參數(shù),即在被調(diào)用程序的參數(shù)表中的形參前加上保留字var。比如:
procedure Calculate(var CalNo : Integer);
則CalNo并不在內(nèi)存中占領(lǐng)一個位置,而是指向?qū)崊umber。當(dāng)一個變參被傳遞時,不論什么對形參所作的改變會反映到實參中。這是由于兩個參數(shù)指向同一個地址。將上一個例程中過程頭的形參CalNo前面加上var,再以同樣的程序調(diào)用它,則在第二個編輯框中會顯示計算的結(jié)果,把第一個編輯框中的數(shù)值放大十倍。這時形參CalNo和實參Number的值都是Nnmber初始值的10倍。
假設(shè)當(dāng)過程或函數(shù)運(yùn)行是要求不改變形參的值,最保險的辦法是使用常量參數(shù)。在參數(shù)表的參數(shù)名稱前加上保留字const能夠使一個形參成為常量參數(shù)。使用常量參數(shù)取代數(shù)值參數(shù)能夠保護(hù)您的參數(shù),使您在不想改變參數(shù)值時不會意外地將新的值賦給這個參數(shù)。
2.1.9 定義新的數(shù)據(jù)類型
Object Pascal有一些系統(tǒng)提前定義的數(shù)據(jù)類型,在2.1.2中已經(jīng)對它們作了介紹。您能夠利用這些數(shù)據(jù)類型以建立新的數(shù)據(jù)類型來滿足程序的特定須要。以下簡單地敘述了您能建立的主要數(shù)據(jù)類型,如枚舉型、子界型、數(shù)組型、集合型、記錄型、對象型等。
2.1.9.1 枚舉類型
一個枚舉型的說明列出了全部這樣的類型能夠包括的值:
type
Tdays=( Sunday ,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday);
能夠定義上述枚舉類型的變量:
var
DayOfWeek:TDays;
在枚舉型中,括號中的每個值都有一個由說明它的位置決定的整形值。比如Sunday有整形值0,Monday有整形值1等。您能夠把DayOfWeek說明為一個整形變量,并將一星期的每一天賦一個整形值以達(dá)到同樣的效果,但用枚舉型會使得程序可讀性好,編寫easy。當(dāng)您在枚舉型中列出值時,您同一時候說明了這個值是一個標(biāo)識符。比如您的程序中假設(shè)已經(jīng)含有TDays類型且說明了DayOfWeeks變量,則程序中便不能使用Monday變量,由于它已經(jīng)被說明為標(biāo)識符了。
2.1.9.2 子界類型
子界型是下列這些類型中某范圍內(nèi)的值:整形、布爾量、字符型或枚舉型。在您想限制一個變量的取值范圍時,子界型是非常實用的。
type
Thours = 0..23;
TValidLetter = 'A' .. 'F';
TDays = ( Sunday ,Monday,Tuesday,Wednesday,Thursday,
Friday,Saturday); {枚舉型}
TWorkDay = Monday..Friday; {一個TDays型的子界}
子界型限定了變量的可能取值范圍。當(dāng)范圍檢查打開時,(在庫單元的Implementation后面有{$R*.DFM}字樣表示范圍檢查打開,否則您能夠在Options|Project|Complier Options中選擇Range Cheking來打開范圍檢查),假設(shè)變量取到子界以外的值,會出現(xiàn)一個范圍檢查錯誤。
2.1.9.3 數(shù)組類型
數(shù)組是某種數(shù)據(jù)類型的有序組合,當(dāng)中每個元素的值由其相對位置來指定,您能夠在數(shù)組的某個位置上放置數(shù)據(jù),并在須要時使用這些數(shù)據(jù)。以下的類型說明了一個Double型的數(shù)組變量:
var
Check : array [1..10] of Double;
它表示Check指向一個含有10個Double型元素的數(shù)據(jù)串列,代表每個元素的是1到10之間的數(shù)字,稱為索引。數(shù)組的每一項由數(shù)組名稱加上[]中的索引來表示。Check包括10個變量,Check[1]表示第一個變量。您也能夠把數(shù)組定義成類型:
type
TCheck = array[1..10] of Double;
則變量說明改為:
var
Check :TCheck;
您能夠通過給數(shù)組賦值等方法來使用數(shù)組。以下的語句將0.0賦給Check數(shù)組中的全部元素:
for J := 1 to 10 do
Check[J] := 0.0;
數(shù)組也能夠是多維的,以下的類型定義了一個20行、20列的數(shù)組。
type
Ttable = array[1..20,1..20] of Double;
var
table1:TTable;
想將這一表格的全部數(shù)據(jù)初始化為0.0,您能夠使用for循環(huán):
var
Col,Row:Integer;
…
for Col :=1 to 20 do
for Row := 1 to 20 do
Table1[Col,Row] := 0.0;
2.1.9.4 字符串類型
字符串類型事實上是一個一維的字符數(shù)組。當(dāng)您說明一個字符串型的變量時,您應(yīng)當(dāng)指明這個字符串的大小,以下是說明字符串類型的樣例:
type
MyString: string[15];
var
MyName: MyString;
則變量MyName被說明成為最多能夠包括15個字符。假設(shè)您沒有說明字符串的大小,Delphi會覺得字符串包括最大值255個字符。給字符串賦值能夠直接使用單引號括起的字串賦值:
MyName := 'Frank.Smith';
或MyName := '張明';
由于MyName是一個能夠包括15個字符的MyString型變量,上文的兩個的變量都是有效的,一個漢字能夠視作兩個字符。當(dāng)您給字符串型變量賦的值多于定義數(shù)值時,比如將MyName賦為‘FrankSmith.Franklin’,則Delphi僅僅會接受前15個字符‘FrankSmith.Fran’。在內(nèi)存中,字符串通常占用比所說明的大小多一個字節(jié)的空間,由于第一個位置是一個包括這個數(shù)組大小的字節(jié)。您能夠使用索引值來訪問字符串的字符,MyName[1]能夠得到MyName的第一個字符'F'。
您能夠使用Delphi豐富的運(yùn)算符、過程和函數(shù)來處理字符串型的變量和屬性。以下介紹幾個經(jīng)常使用的運(yùn)算符和Delphi過程或函數(shù):
Concat和(+)功能同樣,都能夠?qū)⒍鄠€字符串組合在一起,建立一個較大的字符串;Copy會返回一個字符串中的子字符串;Delete在一個字符串中從一個指定位置起刪除一定數(shù)目的字符;Insert在一個字符串中插入一個字符串;Length返回字符串的長度;Pos返回一個子字符串在一個字符串中的位置,即索引值。
2.1.9.5 集合類型
集合類型是一群同樣類型元素的組合,這些類型必須是有限類型如整形、布爾型、字符型、枚舉型和子界型。在檢查一個值是否屬于一個特定集合時,集合類型非常實用。以下的例程能夠說明集合類型的使用方法:
在窗口上增加一個編輯框和一個button,清除編輯框中的文字,在其上加上Caption為“輸入元音”的標(biāo)簽Label,并在編輯框的下方增加一個空的標(biāo)簽,將button的Default屬性改為True,建立button的事件處理步驟例如以下:
procedure TForm1.Button1Click(Sender:TObject);
type
Tvowels=set of Char;
var
Vowels:TVowels;
begin
Vowels := ['a','e','i','o','u'];
if Edit1.Text[1] in Vowels then
Lable2.Caption := '是元音';
else
Lable2.Caption := '請再試';
end;
運(yùn)行這個程序,在編輯框中輸入字母,表達(dá)式Edit1.Text[1] in Vowels的結(jié)果是布爾型的,in是運(yùn)算符,用來推斷字母是否存在于集合中。輸入的判別結(jié)果會顯示在編輯框的下方。以上就用到了集合類型TVowels。
2.1.9.6 記錄類型
記錄是您的程序能夠成組訪問的一群數(shù)據(jù)的集合。以下的例程說明了一個記錄類型的使用方法:
type
TEmployee=record
Name : string[20];
YearHired:1990..2000;
Salsry: Double;
Position: string[20];
end;
記錄包括能夠保存數(shù)據(jù)的域,每個域有一個數(shù)據(jù)類型。上文的記錄TEmployee類型就含有四個域。您能夠用以下的方式說明記錄型的變量:
var
NewEmployee,PromotedEmployee:TEmployee;
用例如以下的方法能夠訪問記錄的單域:
NewEmployee.Salary := 1000;
編寫例如以下的語句能夠給整個記錄賦值:
with PromotedEmployee do
begin
Name :='';
YearHired := 1993;
Salary := 2000.00
Position := 'editor';
end;
您的程序能夠?qū)⒂涗洰?dāng)成單一實體來操作:
PromptEmployee := NewEmployee;
以上介紹了用戶經(jīng)常使用的自己定義類型。在Delphi的編程中,對象是非常重要的用戶自己定義數(shù)據(jù)類型。象記錄一樣,對象是結(jié)構(gòu)化的數(shù)據(jù)類型,它包括數(shù)據(jù)的域(Field),也包括作為方法的過程和函數(shù)。在Delphi中,當(dāng)您向窗口中增加一個部件,也就是向窗口對象中增加了一個域;每個部件也是對象,每當(dāng)您建立一個事件處理過程使得部件能夠響應(yīng)一個事件時,您即自己主動地在窗口中增加了一個方法。在本章第2節(jié)中,將具體講述Delphi面向?qū)ο缶幊痰姆椒ê图记伞?
2.1.10 Object Pascal的庫單元Unit
Units是常量、變量、數(shù)據(jù)類型、過程和函數(shù)的集合,并且能夠被多個應(yīng)用程序所共享。Delphi已經(jīng)擁有很多提前定義的程序庫單元可供您建立您的程序庫單元使用。Delphi的Visual Component Library由多個程序庫單元組成,它們說明了對象、部件以供您的應(yīng)用程序用來設(shè)計用戶界面。比如,當(dāng)您在窗口中增加一個Check Box時,Delphi自己主動在您的程序庫單元中增加了Stdctrls庫單元,由于TCheckBox部件是在StdCtrls庫單元中說明的。
當(dāng)您設(shè)計您的窗口時,Delphi自己主動建立一個和您的窗口有關(guān)的庫單元。您的庫單元不必都和窗口有關(guān),也能夠使用提前定義的僅僅包括數(shù)學(xué)運(yùn)算函數(shù)的庫單元,或是自行編寫數(shù)學(xué)函數(shù)庫單元。在一個庫單元中全部的說明都相互有關(guān)系,比如,CDialogs程序庫單元包括了在您的應(yīng)用程序中使用的普通對話框的全部說明。
2.1.10.1 Object Pascal程序庫單元的結(jié)構(gòu)
無論一個庫單元是否和一個窗口有關(guān),庫單元的結(jié)構(gòu)都是同樣的。其結(jié)構(gòu)例如以下:
unit
interface
uses
{公有說明}
implementation
uses
{私有說明}
{過程和函數(shù)的運(yùn)行部分}
initialization {選擇性的}
{選擇性的初始化程序}
end.
2.1.10.2 程序庫單元的接口部分
interface是庫單元的接口部分,它決定了本庫單元對其它不論什么庫單元或程序的可見(可訪問)部分。您能夠在接口部分說明變量、常量、數(shù)據(jù)類型、過程和函數(shù)等等。Delphi在您設(shè)計窗口的庫單元中,將窗口數(shù)據(jù)類型、窗口變量和事件處理過程都說明在這一部分。
interface標(biāo)志庫單元接口部分的開始。在interface中的說明對要使用這些說明的其它庫單元或應(yīng)用程序是可見的。一個庫單元能夠使用其它Unit的說明,僅僅須要在uses子句中指明那些庫單元就可以。比如,您在庫單元A中編敲代碼代碼,且您想調(diào)用UnitB于interface部分說明的程序。您能夠把庫單元B的名稱增加到A的interface部分的uses子句中,則不論什么A中的程序都能夠調(diào)用B中說明的程序。并且,假設(shè)B中interface部分的uses子句中出現(xiàn)C庫單元,盡管A中未曾出現(xiàn)C,A同樣能夠調(diào)用B、C庫單元在interface中說明的程序。但假設(shè)B出如今A的interface部分的uses子句中,那么庫單元A便不能出如今B的interface的uses子句中。由于這樣會產(chǎn)生對庫單元的循環(huán)訪問。當(dāng)試圖編譯時,會產(chǎn)生出現(xiàn)錯誤信息。
2.1.10.3 程序庫單元的實現(xiàn)部分
實現(xiàn)部分implementation中包括interface中說明的過程、函數(shù)、事件處理過程的具體實現(xiàn)程序代碼。這一部分能夠有自己的額外說明,但這些說明是私有的,外部程序不能調(diào)用這些說明。在interface中說明的函數(shù)實體必須在implementation部分出現(xiàn),能夠使用標(biāo)題簡寫:僅僅輸入procedure或function保留字,后面跟過程或函數(shù)的名稱就可以,其后則是程序的實現(xiàn)部分了。假設(shè)您在implementation部分說明不論什么常式,其標(biāo)題并未出如今interface部分,則必須寫全其標(biāo)題部分。
在implementation部分的uses子句中指定的庫單元,僅僅供給本庫單元的程序使用其interface中說明的程序。其它使用本庫單元的庫單元,不能訪問這些在implementation的udes子句中庫單元的說明,由于在implementation后進(jìn)行的庫單元包括是私有的。所以上例中,假設(shè)C出如今B的implementation部分,則A不能使用C的公有部分,除非C出如今A的uses子句中。在implementation中出現(xiàn)的循環(huán)訪問是Delphi所同意的,假設(shè)A的implemetation的uses子句中出現(xiàn)B,則B的implementation部分也能夠出現(xiàn)A。
2.1.10.4 程序庫單元的初始化部分
初始化當(dāng)前庫單元所使用的數(shù)據(jù),或是通過interface部分將數(shù)據(jù)提供給其它應(yīng)用程序、庫單元使用時,您能夠在庫單元中增加一個initialization部分,在庫單元的end前加上您的初始化語句。當(dāng)一個應(yīng)用程序使用一個庫單元時,在庫單元中的initialization部分會先于其它的代碼運(yùn)行。假設(shè)一個應(yīng)用程序使用了多個庫單元,則每個庫單元的初始化部分都會在全部的程序代碼前運(yùn)行。
2.1.10.5 使用Delphi的可視化部件及其庫單元
當(dāng)您在窗口中增加可視化部件時,假設(shè)該部件在可視化部件庫中,Delphi會在您的庫單元的interface部分的uses子句中自己主動加上須要使用的庫單元名稱。但有些對象在Delphi的環(huán)境中并沒有可視化部件存在,比如,您想在庫單元中增加一個提前定義的信息框,則您必須把MsgDlg庫單元增加您的uses子句中。假設(shè)您要使用TPrinter對象的話,必須將Printer庫單元增加uses子句中。在在線幫助中能夠查到對象所屬的提前定義庫單元。
要使用在其它庫單元中說明的函數(shù),應(yīng)在函數(shù)的前面加上這一庫單元的名稱,并用‘.’號隔開。比如,要在Unit2中使用Unit1中說明的Calculate函數(shù),應(yīng)使用以下的方法:
Number := Unit1.Calculate(10);
您能夠在不論什么標(biāo)識符如屬性、常量、變量、數(shù)據(jù)類型、函數(shù)等之前加上庫單元的名稱。您能夠在自由地在不論什么Delphi庫單元中增加程序代碼,但不要改變由Delphi生成的程序。
2.1.10.6 建立與窗口無關(guān)的新庫單元
假設(shè)您想在project中建立一個和不論什么窗口無關(guān)的新庫單元,能夠現(xiàn)選用File|New Unit。這時一個新的庫單元增加了project,新庫單元的代碼例如以下:
unit Unit2;
interface
implementation
end.
Delphi將依據(jù)您的project中的文件數(shù)目為您的庫單元選擇名稱,您能夠在程序骨架間增加您的程序代碼。
當(dāng)編譯您的project時,這個新增加的庫單元會被編譯為一個具有.DCU后綴的文件。這個新生成的文件是鏈接到project的可運(yùn)行文件上的機(jī)器代碼。
2.1.10.7 將庫單元增加project
將庫單元增加project是比較簡單的。無論是您自己建立的庫單元還是Delphi建立的與窗口有關(guān)的庫單元,假設(shè)已經(jīng)完畢,則先打開您想增加庫單元的project(能夠用Open Project打開project);再選用File|Open File,然后選擇您想增加的源程序(.PAS文件),并選擇OK就可以。則庫單元被增加到應(yīng)用程序中。
2.2 用Delphi的對象進(jìn)行編程
Delphi是基于面向?qū)ο缶幊痰南冗M(jìn)開發(fā)環(huán)境。面向?qū)ο蟮某绦蛟O(shè)計(OOP)是結(jié)構(gòu)化語言的自然延伸。OOP的先進(jìn)編程方法,會產(chǎn)生一個清晰而又easy擴(kuò)展及維護(hù)的程序。一旦您為您的程序建立了一個對象,您和其它的程序猿能夠在其它的程序中使用這個對象,全然不必又一次編制繁復(fù)的代碼。對象的反復(fù)使用能夠大大地節(jié)省開發(fā)時間,切實地提高您和其它人的工作效率。
2.2.1 什么是對象
一個對象是一個數(shù)據(jù)類型。對象就象記錄一樣,是一種數(shù)據(jù)結(jié)構(gòu)。按最簡單的理解,我們能夠?qū)ο罄斫獬梢粋€記錄。但實際上,對象是一種定義不確切的術(shù)語,它經(jīng)常使用來定義抽象的事務(wù),是構(gòu)成應(yīng)用程序的項目,其內(nèi)涵遠(yuǎn)比記錄要豐富。在本書中,對象可被理解為可視化部件如button、標(biāo)簽、表等。
了解對象,最關(guān)鍵的是掌握對象的特性。一個對象,其最突出的特征有三個:封裝性、繼承性、多態(tài)性。
2.2.1.1 對象的封裝性
對對象最基本的理解是把數(shù)據(jù)和代碼組合在同一個結(jié)構(gòu)中,這就是對象的封裝特性。將對象的數(shù)據(jù)域封閉在對象的內(nèi)部,使得外部程序必需并且僅僅能使用正確的方法才干對要讀寫的數(shù)據(jù)域進(jìn)行訪問。封裝性意味著數(shù)據(jù)和代碼一起出如今同一結(jié)構(gòu)中,假設(shè)須要的話,能夠在數(shù)據(jù)周圍砌上“圍墻”,僅僅實用對象類的方法才干在“圍墻”上打開缺口。
2.2.1.2 對象的繼承性
繼承性的含義直接并且顯然。它是指把一個新的對象定義成為已存在對象的后代;新對象繼承了舊類的一切東西。在往新對象中增加不論什么新內(nèi)容曾經(jīng),父類的每個字段和方法都已存在于子類中,父類是創(chuàng)建子類的基石。
2.2.1.3 對象的多態(tài)性
多態(tài)性是在對象體系中把設(shè)想和實現(xiàn)分開的手段。假設(shè)說繼承性是系統(tǒng)的布局手段,多態(tài)性就是其功能實現(xiàn)的方法。多態(tài)性意味著某種概括的動作能夠由特定的方式來實現(xiàn),這取決于運(yùn)行該動作的對象。多態(tài)性同意以相似的方式處理類體系中相似的對象。依據(jù)特定的任務(wù),一個應(yīng)用程序被分解成很多對象,多態(tài)性把高級設(shè)計處理的設(shè)想如新對象的創(chuàng)建、對象在屏幕上的重顯、程序運(yùn)行的其它抽象描寫敘述等,留給知道該怎樣完美的處理它們的對象去實現(xiàn)。
2.2.1.4 通過Delphi實例了解對象
讓我們結(jié)合Delphi的實例討論對象的概念:
當(dāng)您要建立一個新project時,Delphi 將顯示一個窗口作為設(shè)計的基礎(chǔ)。在程序編輯器中,Delphi將這個窗口說明為一個新的對象類型,并同一時候在與窗口相關(guān)聯(lián)的庫單元中生成了創(chuàng)建這個新窗口對象的程序代碼。
unit Unit1;
interface
uses SysUtils, Windows, Messages, Classes, Graphics, Controls, Forms, Dialogs;
type
TForm1 = class(TForm) {窗口的類型說明開始}
private
{ Private declarations }
public
{ Public declarations }
end; {窗口的類型說明結(jié)束}
var
Form1: TForm1; {說明一個窗口變量}
implementation
{$R *.DFM}
end.
新的窗口類型是TForm1,它是從TForm繼承下來的一個對象。它具有對象的特征:含有域或方法。由于您未給窗口增加不論什么部件,所以它僅僅有從TForm類中繼承的域和方法,在窗口對象的類型說明中,您是看不到不論什么域、方法的說明的。Form1稱為TForm1類型的實例(instance)。您能夠說明多個對象類型的實例,比如在多文檔界面(MDI)中管理多個子窗口時就要進(jìn)行這樣的說明。每個實例都有自己的說明,但全部的實例卻共用同樣的代碼。
假設(shè)您向窗口中增加了一個button部件,并對這個button建立了一個OnClick事件處理過程。再查看Unit1的源程序,會發(fā)現(xiàn)TForm1的類型說明部分例如以下:
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
如今TForm1對象有了一個名為Button1的域:它是您在窗口中增加的button。TButton是一個對象類型,Button1是Tbutton的一個實例。它被TForm1對象所包括,作為它的數(shù)據(jù)域。每當(dāng)您在窗口中增加一個部件時,部件的名稱就會作為TFom1的域增加到類型說明中來。在Delphi中,您所編寫的事件處理過程都是窗口對象的方法。每當(dāng)您建立一個事件處理過程,就會在窗口的對象類型中說明一個方法。
當(dāng)您使用Object Inspector來改變對象(部件)的名稱時,這個名稱的改變會反映到程序中。比如,在Object Inspector中將Form1的Name屬性命名為ColorBox,您會發(fā)如今類型說明部分,會將前文的TForm1改為:
TColorBox=class(TForm);
并且在變量說明部分,會說明ColorBox為TColorBox類型的變量,由Delphi自己主動產(chǎn)生的事件處理過程名稱會自己主動改為TColorBox.Button1Click;但您自行編寫的實現(xiàn)部分的代碼卻不會被自己主動改動。因此,假設(shè)您在改變Name屬性前編寫了程序,則您必須將事件處理過程中的對象名稱進(jìn)行改變。所以,原先的Form1.Color要改為ColorBox.Color。
2.2.2 從一個對象中繼承數(shù)據(jù)和方法
前面的TForm1類型是非常簡單的,由于它僅僅含有域Button1和方法Button1Click。可是在這個窗口上,您能夠改變窗口的大小、增加或刪除窗口的最大最小化button,或設(shè)置這個窗口為MDI界面。對于一個僅僅包括一個域和方法的對象來講,您并沒有看到顯式的支持程序。在窗口上單擊鼠標(biāo)或用Object Inspector的上端的Object Selector選中Form1對象,按動F1查閱它的在線幫助,您會在Properties和Method中找到它的繼承到的全部屬性和方法。這些是在TForm類型中說明的,TForm1是TForm的子類,直接繼承了它全部的域、方法、屬性和事件。比如窗口的顏色屬性Color就是在TForm中說明的。當(dāng)您在project中增加一個新窗口時,就等于增加了一個基本模型。通過不斷地在窗口中增加部件,您就自行定義了一個新的窗口。要自己定義不論什么對象,您都將從已經(jīng)存在的對象中繼承域和方法,建立一個該種對象的子類。比如對象TForm1就被說明為對象TForm的子類,擁有一個窗口部件的基本屬性或方法。僅僅有當(dāng)您在窗口中增加了部件或編寫了事件處理過程時,Form1才成為您自己的類型。
一個比較特殊的對象是從一個范圍較廣或較一般的對象中繼承下來的,它是這個特別對象的祖先,這個對象則稱為祖先的后代。一個對象僅僅能有一個直接的祖先,可是它能夠有很多后代。TForm是TForm1類型的祖先,全部的窗口對象都是TForm的后代。
用F1查閱窗口的在線幫助時,您會發(fā)現(xiàn)TForm被稱為component(部件)。這是由于全部的部件都是對象。
在這個結(jié)構(gòu)中全部的部件都是對象。部件類型TComponent從TObject類型中繼承數(shù)據(jù)和程序代碼,并具有額外的能夠用作特殊用途的屬性、方法、事件,所以部件能夠直接和用戶打交道,記錄它的狀態(tài)并存貯到文件里等等。控制類型TControl從TComponent中繼承而來,又增加了新的功能,如它能夠顯示一個對象。在上圖中,盡管TCheckBox不是直接由TObject繼承來的,可是它仍然有不論什么對象所擁有的屬性,由于在VCL結(jié)構(gòu)中,TCheckBox終究還是從TObject 中繼承了全部功能的特殊對象,但它還有些自行定義的獨(dú)到的功能,如能夠選擇記錄狀態(tài)等。
2.2.3 對象的范圍
2.2.3.1 關(guān)于對象的范圍
一個對象的范圍決定了它的數(shù)據(jù)域、屬性值、方法的活動范圍和訪問范圍。在一個對象的說明部分說明的數(shù)據(jù)域、屬性值、方法都僅僅是在這個對象的范圍中,并且僅僅有這個對象和它的后代才干擁有它們。盡管這些方法的實際程序代碼可能是在這個對象之外的程序庫單元中,但這些方法仍然在這個對象的范圍內(nèi),由于它們是在這個對象的說明部分中說明的。
當(dāng)您在一個對象的事件處理過程中編敲代碼代碼來訪問這個對象的屬性值、方法或域時,您不須要在這些標(biāo)識符之前加上這個對象變量的名稱。比如,假設(shè)您在一個新窗口上增加一個button和一個編輯框,并為這個button編寫OnClick事件處理過程:
procedure TForm1.Button1Click(Sender:Tobject);
begin
Color :=clFuchsia;
Edit1.Color :=clLime;
end;
當(dāng)中的第一行語句是為整個窗口Form1著色。您也能夠編寫例如以下:
Form1.Color :=clFuchsia;
但您能夠不必加上Form1.,由于Button1Click方法是在TForm1對象的范圍里。當(dāng)您在一個對象的范圍中時,您能夠省略全部這個對象中的屬性值、方法、域之前的對象標(biāo)識符。可是當(dāng)您編寫第二個語句改變編輯框的底色時,由于此時您想訪問的是TEdit1對象的Color屬性,而不是TForm1類型的,所以您須要通過在屬性前面加上編輯框的名稱來指明Color屬性值的范圍。假設(shè)不指明,Delphi會象第一個語句一樣,將窗口的顏色變成綠色。由于Edit1部件是在窗口中的,它是窗口的一個數(shù)據(jù)域,所以您同樣不必指明其從屬關(guān)系。
假設(shè)Edit1是在其它窗口中,那么您須要在編輯框之前加上這個船體對象的名稱了。比如,假設(shè)Edit1是在Form2之中,那它是Form2說明的一個數(shù)據(jù)域,并位于Form2的范圍中,那么您須要將第二句改為:
Form2.Edit1.Color := clLime;
并且須要把Unit2增加Unit1的uses子句中。
一個對象的范圍擴(kuò)展到這個對象的全部后代。TForm的全部屬性值、方法和事件都在TForm1的范圍中,由于TForm1是TForm的后代。您的應(yīng)用程序不能說明和祖先的數(shù)據(jù)域重名的類型、變量等。假設(shè)Delphi顯示了一個標(biāo)識符被反復(fù)定義的信息,就有可能是一個數(shù)據(jù)域和其祖先對象(比如TForm)的一個數(shù)據(jù)域有了同樣的名稱。能夠嘗試改變這個標(biāo)識符的名稱。
2.2.3.2 重載一個方法
您能夠重載(Override)一個方法。通過在后代對象中說明一個與祖先對象重名的方法,就能夠重載一個方法。假設(shè)想使這種方法在后代對象中作和祖先對象中一樣的工作可是使用不同的方式時,您就能夠重載這種方法。Delphi不推薦您經(jīng)常重載方法,除非您想建立一個新的部件。重載一個方法,Delphi編譯器不會給出錯誤或警告提示信息。
2.2.4 對象公有域和私有域的說明
當(dāng)使用Delphi的環(huán)境來建立應(yīng)用程序時,您能夠在一個TForm的后代對象中增加數(shù)據(jù)域和方法,也能夠通過直接改動對象類型說明的方法來為一個對象加上域和方法,而不是把一個部件增加窗口或事件處理過程中。
您能夠在對象的Public或Private部分增加新的數(shù)據(jù)域和方法。Public和Private是Object Pascal的保留字。當(dāng)您在project中增加新的窗口時,Delphi開始建立這個新窗口對象。每個新的對象都包括public和private指示,以便您在代碼中增加數(shù)據(jù)域和方法。在public部分中說明其它庫單元中對象的方法也能夠訪問的數(shù)據(jù)域或方法。在private部分的說明有訪問的限制。假設(shè)您在private中說明域和方法,那么它在說明這個對象的庫單元外是不透明的,并且不能被訪問。private中能夠說明僅僅能被本庫單元方法訪問的數(shù)據(jù)域和本庫單元對象訪問的方法。過程或函數(shù)的程序代碼能夠放在庫單元的implementation部分。
2.2.5 訪問對象的域和方法
當(dāng)您想要改變一個窗口對象的一個域的某個屬性,或是調(diào)用它的一個方法時,您必須在這個屬性名稱或調(diào)用方法之前加上這個對象的名稱。比如,假設(shè)您的窗口上有一個編輯框部件,而您須要在運(yùn)行中改變它的Text屬性,須要編寫下列的代碼:
Edit1.Text := 'Welcome to Delphi';
同樣,清除編輯框部件中選中的文本,能夠調(diào)用TEdit部件的對應(yīng)方法:
Edit1.ClearSelection;
假設(shè)您想改變一個窗口對象中一個對象域的多個屬性或調(diào)用多個方法時,使用with語句能夠簡化您的程序。with語句在對象中能夠和在記錄中一樣方便地使用。以下的事件處理過程在響應(yīng)OnClick事件時,會對一個列表框作多個調(diào)整:
procedure TForm1.Button1Click(Sender:TObject);
begin
ListBox1.Clear;
ListBox1.MultiSelect :=True;
ListBox1.Item.Add('One');
ListBox1.Item.Add('Two');
ListBox1.Item.Add('Three');
ListBox1.Sorted :=Ture;
ListBox1.FontStyle :=[fsBold];
ListBox1.Font.Color :=clPurple;
ListBox1.Font.Name :='Times New Roman';
ListBox1.ScaleBy(125,100);
end;
假設(shè)使用了With語句,則程序例如以下:
procedure TForm1.Button1Click(Sender:TObject);
begin
with (ListBox1) do
begin
Clear;
MultiSelect :=True;
Item.Add('One');
Item.Add('Two');
Item.Add('Three');
Sorted :=Ture;
FontStyle :=[fsBold];
Font.Color :=clPurple;
Font.Name :='Times New Roman';
ScaleBy(125,100);
end;
end;
使用with語句,您不必在每個屬性或方法前加上ListBox1標(biāo)識符,在With語句之內(nèi),全部的屬性或調(diào)用方法對于ListBox這個對象而言都是在它的范圍內(nèi)的。
2.2.6 對象變量的賦值
假設(shè)兩個變量類型同樣或兼容,您能夠把當(dāng)中一個對象變量賦給還有一個對象變量。比如,對象TForm1和TForm2都是從TForm繼承下來的類型,并且Form1和Form2已被說明過,那么您能夠把Form1賦給Form2:
Form2 :=Form1;
僅僅要賦值的對象變量是被賦值的對象變量的祖先類型,您就能夠?qū)⒁粋€對象變量賦給還有一個對象變量。比如,以下是一個TDataForm的類型說明,在變量說明部分一共說明了兩個變量:AForm和DataForm。
type
TDataForm = class(TForm)
Button1:TButton;
Edit1:TEdit;
DataGrid1:TDataGrid;
Database1:TDatabase;
TableSet1:TTableSet;
VisibleSession1:TVisibleSession;
private
{私有域說明}
public
{公有域說明}
end;
var
AForm:TForm;
DataForm:TDataForm;
由于TDataForm是TForm類型的后代,所以Dataform是AForm的后代,因此以下的賦值語句是合法的:
AForm :=DataForm;
這一點在Delphi中是極為重要的。讓我們來看一下應(yīng)用程序調(diào)用事件處理過程的過程,以下是一個button部件的OnClick事件處理過程:
procedure TForm1.Button1Click(Sender:TObject);
begin
end;
您能夠看到TObject類在Delphi的Visual Component Library的頂部,這就意味著全部的Delphi對象都是TObject的后代。由于Sender是TObject類型,所以不論什么對象都能夠賦值給它。盡管您沒有看見賦值的程序代碼,但事實上發(fā)生事件的部件或控制部件已經(jīng)賦給Sender了,這就是說Sender的值是響應(yīng)發(fā)生事件的部件或控制部件的。
您能夠使用保留字is來測試Sender以便找到調(diào)用這個事件處理過程的部件或控制部件的類型。Delphi中的一個顯示drag-and-drop的DRAGDROP.DPRproject。載入它,能夠查閱到DROPFONT.PAS庫單元的代碼,在Memo1DragOver方法中檢查了一個對象變量的類型。在這樣的情形下,參數(shù)是Source而不是Sender。
procrdure TForm1.Memo1DragOver(SenderSource:TObject;X,Y:integer;
State:TDragState;var Accept:Boolean);
begin
Accept :=Source is TLabel;
end;
Source參數(shù)也是TObject類型,Source被賦值為那個被拖曳的對象。用Memo1DragOver方法的目的是確保僅僅有標(biāo)簽?zāi)軌虮煌弦贰ccept是布爾型參數(shù),假設(shè)Accept為True,那么用戶選擇的部件能夠被拖曳;反之當(dāng)Accept的值為False時,用戶就不能夠拖曳選擇控制部件。is保留字檢查Source是否TLabel的類型,所以Accept僅僅有在用戶拖曳一個標(biāo)簽時才為真,并作為變參輸出到函數(shù)之外。
以下的drag-and-drop展示的Memo1DragDrop事件處理過程中也使用了Source參數(shù)。這種方法是為了把Memo部件的字型改變成和放入這個備注控制部件的標(biāo)簽一樣的字型:
procedure TForm1.Memo1DragDrop(SenderSource:TObject;
X,Y:Integer);
begin
Memo1.Font := (Source as TLabel).Font;
end;
當(dāng)您在這個事件處理過程中編寫賦值語句時,開發(fā)者并不知道用戶會放入哪一個標(biāo)簽,僅僅有通過參考這個標(biāo)簽的名稱(Source as TLabel)用戶才干知道,并把標(biāo)簽類型賦給Memo1.TFont。Source包括了用戶拖放控制部件的名稱,僅僅有當(dāng)Source是一個標(biāo)簽時,這個事件處理過程才同意這個賦值發(fā)生。
2.2.7 建立非可視化對象
您在Delphi中使用的大部分對象都是您在設(shè)計和運(yùn)行期間能夠看見的部件,比如編輯框、button等;一些部件,如通用對話框(Common dialog box)等,在設(shè)計時看不見,而在運(yùn)行時能夠看見;另外有些部件,比如計時器(Timer)、數(shù)據(jù)源(Data Source)部件等,在程序的運(yùn)行期間沒有不論什么可視化的顯示,但您卻能夠在您的應(yīng)用程序中使用它們。
2.2.7.1說明一個非可視化對象
以下,通過一個簡單的樣例講述怎樣建立自己的非可視化對象:
您能夠用例如以下的方法,建立一個自己的TEmployee非可視化對象:
type
Temployee = class(TObject);
Name := String[25];
Title := String[25];
HourlyPayRate : Double;
function CalculatePayAmount:Double;
end;
在這樣的情況下,TEmployee從TObject繼承下來,且包括三個域和一個方法。把您建立的類型說明放在庫單元中的說明部分,并和窗口說明放在一起。在這個程序庫單元的變量說明部分,說明一個新類型的變量:
var
Employee : TEmployee;
2.2.7.2用Create方法建立對象實例
TEmployee僅僅是一個對象類型。除非通過一個構(gòu)造函數(shù)的調(diào)用從而被實例取代或創(chuàng)建,否則一個對象并不存儲在內(nèi)存中。構(gòu)造函數(shù)是一個方法,它為新對象配置內(nèi)存并且指向這個新的對象。這個新的對象也被稱為這個對象類型的一個實例。
建立一個對象的實例,須要調(diào)用Create方法,然后構(gòu)造函數(shù)把這個實例賦給一個變量。假設(shè)您想說明一個TEmployee類型的實例,在您訪問這個對象的不論什么域之前,您的程序代碼必須調(diào)用Create。
Employee := TEmployee.Create;
Create方法并沒有在TEmployee類型中說明,它繼承自TObject類型。由于TEmployee是TObject的子類,所以它能夠調(diào)用Create方法而創(chuàng)建一個TEmployee實例。然后把它賦給Employee變量。在創(chuàng)建了一個這樣的對象后,您就能夠象使用其它的Delphi對象一樣訪問Employee對象了。
2.2.7.3 撤銷對象
當(dāng)您使用完對象后,您應(yīng)該及時撤銷它,以便把這個對象占用的內(nèi)存釋放出來。您能夠通過調(diào)用一個注銷方法來撤銷您的對象,它會釋放分配給這個對象的內(nèi)存。
Delphi的注銷方法有兩個:Destroy和Free。Delphi建議使用Free,由于它比Destroy更為安全,同一時候調(diào)用Free會生成效率更高的代碼。
您能夠用下列的語句釋放用完的Employee對象:
Employee.Free;
和Create方法一樣,Free方法也是TEmployee從TObject中繼承過來的。把您的注銷放在try…finally程序模塊的finally部分,而把對象的程序代碼放在try部分是編程的好習(xí)慣。這樣,即使您的程序代碼在使用對象時發(fā)生了異常事件,也會確保您為這個對象分配的內(nèi)存會被釋放。關(guān)于異常處理和try…finally程序模塊的信息以及建立非可視化對象的樣例,在后文中還將細(xì)致講述。
用hook實現(xiàn)dll注入具體解釋
baidu
須要一個用來注入的dll(inject.dll)及一個調(diào)用程序(caller.exe)
流程:
caller.exe
procedure TestHook;
var pwnd,hChild, hwndInject :hwnd;
msg:tmsg;
begin
//通過窗口標(biāo)題用FindWindow找到要注入的程序的主窗口句柄pwnd
pwnd := findwindow('Progman',nil);
//用FindwindowEx(hMain,0,nil,nil)找到要處理的子窗口句柄hChild
hChild := findWindowEx(pwnd,0,nil,nil);
//用getwindowThreadProcessid(hChild,nil)找到要注入的線程
dwThreadID := getwindowThreadProcessid(hChild,nil);
//調(diào)用 inject.dll的SetInjectHook方法
SetInjectHook(dwThreadID);
//等待消息返回
getmessage(msg,0,0,0);
//找到注入的窗口
hwndInject:= findwindow(nil,'InjectForm');
//發(fā)送控制消息,將目標(biāo)窗口的句柄作為wparam,控制參數(shù)以lparam傳入
sendMessage( hwndInject, wm_app,hChild,integer(true));
//關(guān)閉注入的窗口
sendMessage( hwndInject,wm_close,0,0);
//等待窗口關(guān)閉
sleep(500);
//檢查是否成功關(guān)閉
assert(not iswindow( hwndInject));
//去掉掛鉤
setDipsHook(0);
end;
//以下說明 Inject.dll的SetInjectHook的具體操作
在全局定義以下變量
var
g_hhook :Hhook=0;
g_dwThreadidInject :dword=0;
g_hInjectfrm:hwnd;
function SetInjectHook(dwThreadid:DWORD):boolean;
begin
result := false;
//假設(shè)線程標(biāo)志為0則用于去掉鉤子,否則進(jìn)行動態(tài)庫注入
if dwThreadid<>0 then
begin
assert(g_hhook=0);
//保存當(dāng)前線程的ID到 g_dwThreadidInject
g_dwThreadidInject := getCurrentThreadid;
//下一個GetMessage的鉤子到目標(biāo)線程
//GetMsgProc是在以下定義的一個函數(shù),在第一次調(diào)用時將自己定義的form在目標(biāo)線程中創(chuàng)建出來
//這樣就能通過這個自己定義的form對目標(biāo)線程進(jìn)行進(jìn)程內(nèi)控制了
g_hhook := setWindowsHookEx(wh_getMessage,GetMsgProc,hInstance,dwThreadid);
result := g_hhook <> null;
if result then
//發(fā)一個空的信息以便于立即創(chuàng)建這個自己定義form
result := postThreadMessage(dwThreadid, wm_Null,0,0);
//等待半秒鐘,以保證調(diào)用者能夠找到這個剛創(chuàng)建的form
sleep(500);
end else
begin
assert(g_hhook<>0);
//去掉鉤子
result := unHookWindowsHookEx(g_hhook);
g_Hhook := 0;
end;
end;
//定義一個全局的是否第一個消息的標(biāo)志
var
fFirstTime:boolean = true;
//這個函數(shù)用于在收到第一個消息時創(chuàng)建自己定義窗口,以便于遠(yuǎn)程控制
function GetMsgProc(code: Integer; wparam: WPARAM; lparam: LPARAM): LRESULT; stdcall;
begin
//假設(shè)是第一次
if fFirstTime then
begin
fFirstTime := false;
//創(chuàng)建窗口
InjectFrm := TinjectFrm.create(nil);
//保存窗口句柄
g_hInjectfrm := InjectFrm.handle;
end;
//調(diào)用默認(rèn)處理,這一句可不能忘記
result := callNexthookEx(g_hhook,code,wparam,lparam);
end;
《COM 原理與應(yīng)用》學(xué)習(xí)筆記 - 第一部分 COM原理
baidu
⊙ 第一章 概述
===================================================
COM 是什么
---------------------------------------------------
COM 是由 Microsoft 提出的組件標(biāo)準(zhǔn),它不僅定義了組件程序之間進(jìn)行交互的標(biāo)準(zhǔn),并且也提供了組件程序運(yùn)行所需的環(huán)境。在 COM 標(biāo)準(zhǔn)中,一個組件程序也被稱為一個模塊,它能夠是一個動態(tài)鏈接庫,被稱為進(jìn)程內(nèi)組件(in-process component);也能夠是一個可運(yùn)行程序(即 EXE 程序),被稱作進(jìn)程外組件(out-of-process component)。一個組件程序能夠包括一個或多個組件對象,由于 COM 是以對象為基本單元的模型,所以在程序與程序之間進(jìn)行通信時,通信的兩方應(yīng)該是組件對象,也叫做 COM 對象,而組件程序(或稱作 COM 程序)是提供 COM 對象的代碼載體。
COM 對象不同于一般面向?qū)ο笳Z言(如 C++ 語言)中的對象概念,COM 對象是建立在二進(jìn)制可運(yùn)行代碼級的基礎(chǔ)上,而 C++ 等語言中的對象是建立在源碼級基礎(chǔ)上的,因此 COM 對象是語言無關(guān)的。這一特性使用不同編程語言開發(fā)的組件對象進(jìn)行交互成為可能。
---------------------------------------------------
COM 對象與接口
---------------------------------------------------
相似于 C++ 中對象的概念,對象是某個類(class)的一個實例;而類則是一組相關(guān)的數(shù)據(jù)和功能組合在一起的一個定義。使用對象的應(yīng)用(或還有一個對象)稱為客戶,有時也稱為對象的用戶。
接口是一組邏輯上相關(guān)的函數(shù)集合,其函數(shù)也被稱為接口成員函數(shù)。依照習(xí)慣,接口名常是以“I”為前綴。對象通過接口成員函數(shù)為客戶提供各種形式的服務(wù)。
在 COM 模型中,對象本身對于客戶來說是不可見的,客戶請求服務(wù)時,僅僅能通過接口進(jìn)行。每個接口都由一個 128 位的全局唯一標(biāo)識符(GUID,Global Unique Identifier)來標(biāo)識。客戶通過 GUID 來獲得接口的指針,再通過接口指針,客戶就能夠調(diào)用其對應(yīng)的成員函數(shù)。
COM 原理與應(yīng)用》學(xué)習(xí)筆記 - 第一部分 COM原理
baidu
與接口相似,每個組件也用一個 128 位 GUID 來標(biāo)識,稱為 CLSID(class identifer,類標(biāo)識符或類 ID),用 CLSID 標(biāo)識對象能夠保證(概率意義上)在全球范圍內(nèi)的唯一性。實際上,客戶成功地創(chuàng)建對象后,它得到的是一個指向?qū)ο竽硞€接口的指針,由于 COM 對象至少實現(xiàn)一個接口(沒有接口的 COM 對象是沒有意義的),所以客戶就能夠調(diào)用該接口提供的全部服務(wù)。依據(jù) COM 規(guī)范,一個 COM 對象假設(shè)實現(xiàn)了多個接口,則能夠從某個接口得到該對象的隨意其它接口。從這個過程我們也能夠看出,客戶與 COM 對象僅僅通過接口打交道,對象對于客戶來說僅僅是一組接口。
---------------------------------------------------
COM 進(jìn)程模型
---------------------------------------------------
COM 所提供的服務(wù)組件對象在實現(xiàn)時有兩種進(jìn)程模型:進(jìn)程內(nèi)對象和進(jìn)程外對象。假設(shè)是進(jìn)程內(nèi)對象,則它在客戶進(jìn)程空間中運(yùn)行;假設(shè)是進(jìn)程外對象,則它運(yùn)行在同機(jī)器上的還有一個進(jìn)程空間或者在遠(yuǎn)程機(jī)器的空間。
進(jìn)程內(nèi)服務(wù)程序:
服務(wù)程序被載入到客戶的進(jìn)程空間,在 Windows 環(huán)境下,通常服務(wù)程序的代碼以動態(tài)連接庫(DLL)的形式實現(xiàn)。
本地服務(wù)程序:
服務(wù)程序與客戶程序運(yùn)行在同一臺機(jī)器上,服務(wù)程序是一個獨(dú)立的應(yīng)用程序,通常它是一個 EXE 文件。
遠(yuǎn)程服務(wù)程序:
服務(wù)程序運(yùn)行在與客戶不同的機(jī)器上,它既能夠是一個 DLL 模塊,也能夠是一個 EXE 文件。假設(shè)遠(yuǎn)程服務(wù)程序是以 DLL 形式實現(xiàn)的話,則遠(yuǎn)程機(jī)器會創(chuàng)建一個代理進(jìn)程。
盡管 COM 對象有不同的進(jìn)程模型,但這樣的差別對于客戶程序來說是透明的,因此客戶程序在使用組件對象時能夠無論這樣的差別的存在,僅僅要遵照 COM 規(guī)范就可以。然而,在實現(xiàn) COM 對象時,還是應(yīng)該謹(jǐn)慎選擇進(jìn)程模型。進(jìn)程內(nèi)模型的長處是效率高,但組件不穩(wěn)定會引起客戶進(jìn)程崩潰,因此組件可能會危及客戶;(savetime 注:這里有點問題,假設(shè)組件不穩(wěn)定,進(jìn)程外模型也同樣會出問題,可能是由于進(jìn)程內(nèi)組件和客戶同處一個地址空間,出現(xiàn)沖突的可能性比較大?)進(jìn)程外模型的長處是穩(wěn)定性好,組件進(jìn)程不會危及客戶程序,一個組件進(jìn)程能夠為多個客戶進(jìn)程提供服務(wù),但進(jìn)程外組件開銷大,并且調(diào)用效率相對低一點。
《COM 原理與應(yīng)用》學(xué)習(xí)筆記 - 第一部分 COM原理
baidu
---------------------------------------------------
COM 可重用性
---------------------------------------------------
由于 COM 標(biāo)準(zhǔn)是建立在二進(jìn)制代碼級的,因此 COM 對象的可重用性與一般的面向?qū)ο笳Z言如 C++ 中對象的重用過程不同。對于 COM 對象的客戶程序來說,它僅僅是通過接口使用對象提供的服務(wù),它并不知道對象內(nèi)部的實現(xiàn)過程,因此,組件對象的重用性可建立在組件對象的行為方式上,而不是具體實現(xiàn)上,這是建立重用的關(guān)鍵。COM 用兩種機(jī)制實現(xiàn)對象的重用。我們假定有兩個 COM 對象,對象1 希望能重用對象2 的功能,我們把對象1 稱為外部對象,對象2 稱為內(nèi)部對象。
(1)包容方式。
對象1 包括了對象2,當(dāng)對象1 須要用到對象2 的功能時,它能夠簡單地把實現(xiàn)交給對象2 來完畢,盡管對象1 和對象2 支持同樣的接口,但對象1 在實現(xiàn)接口時實際上調(diào)用了對象2 的實現(xiàn)。
(2)聚合方式。
對象1 僅僅需簡單地把對象2 的接口遞交給客戶就可以,對象1 并沒有實現(xiàn)對象2 的接口,但它把對象2 的接口也暴露給客戶程序,而客戶程序并不知道內(nèi)部對象2 的存在。
===================================================
⊙ 第二章 COM 對象模型
===================================================
全局唯一標(biāo)識符 GUID
---------------------------------------------------
COM 規(guī)范採用了 128 位全局唯一標(biāo)識符 GUID 來標(biāo)識對象和接口,這是一個隨機(jī)數(shù),并不須要專門機(jī)構(gòu)進(jìn)行分配和管理。由于 GUID 是個隨機(jī)數(shù),所以并不絕對保證唯一性,但發(fā)生標(biāo)識符相重的可能性非常小。從理論上講,假設(shè)一臺機(jī)器每秒產(chǎn)生 10000000 個 GUID,則能夠保證(概率意義上)的 3240 年不反復(fù))。
GUID 在 C/C++ 中能夠用這樣的結(jié)構(gòu)來描寫敘述:
typedef struct _GUID
{
DWORD Data1;
WORD Data2;
WORD Data3;
BYTE Data4[8];
} GUID;
例:{64BF4372-1007-B0AA-444553540000} 能夠例如以下定義一個 GUID:
extern "C" const GUID CLSID_MYSPELLCHECKER =
{ 0x54BF0093, 0x1048, 0x399D,
{ 0xB0, 0xA3, 0x45, 0x33, 0x43, 0x90, 0x47, 0x47} };
Visual C++ 提供了兩個程序生成 GUID: UUIDGen.exe(命令行) 和 GUIDGen.exe(對話框)。COM 庫提供了以下 API 函數(shù)能夠產(chǎn)生 GUID:
HRESULT CoCreateGuid(GUID *pguid);
假設(shè)創(chuàng)建 GUID 成功,則函數(shù)返回 S_OK,并且 pguid 將指向所得的 GUID 值。
---------------------------------------------------
COM 對象
---------------------------------------------------
在 COM 規(guī)范中,并沒有對 COM 對象進(jìn)行嚴(yán)格的定義,但 COM 提供的是面向?qū)ο蟮慕M件模型,COM 組件提供給客戶的是以對象形式封裝起來的實體。客戶程序與 COM 程序進(jìn)行交互的實體是 COM 對象,它并不關(guān)心組件模型的名稱和位置(即位置透明性),但它必須知道自己在與哪個 COM 對象進(jìn)行交互。
---------------------------------------------------
COM 接口
---------------------------------------------------
從技術(shù)上講,接口是包括了一組函數(shù)的數(shù)據(jù)結(jié)構(gòu),通過這組數(shù)據(jù)結(jié)構(gòu),客戶代碼能夠調(diào)用組件對象的功能。接口定義了一組成員函數(shù),這組成員函數(shù)是組件對象暴露出來的全部信息,客戶程序利用這些函數(shù)獲得組件對象的服務(wù)。
通常我們把接口函數(shù)表稱為虛函數(shù)表(vtable),指向 vtable 的指針為 pVtable。對于一個接口來說,它的虛函數(shù)表是確定的,因此接口的成員函數(shù)個數(shù)是不變的,并且成員函數(shù)的先后先后順序也是不變的;對于每個成員函數(shù)來說,其參數(shù)和返回值也是確定的。在一個接口的定義中,全部這些信息都必須在二進(jìn)制一級確定,無論什么語言,僅僅要能支持這樣的內(nèi)存結(jié)構(gòu)描寫敘述,就能夠使用接口。
接口指針 ----> pVtable ----> 指針函數(shù)1 -> |----------|
m_Data1 指針函數(shù)2 -> | 對象實現(xiàn) |
m_Data2 指針函數(shù)3 -> |----------|
每個接口成員函數(shù)的第一個參數(shù)為指向?qū)ο髮嵗闹羔?=this),這是由于接口本身并不獨(dú)立使用,它必須存在于某個 COM 對象上,因此該指針能夠提供對象實例的屬性信息,在被調(diào)用時,接口能夠知道是對哪個 COM 對象在進(jìn)行操作。
在接口成員函數(shù)中,字符串變量必須用 Unicode 字符指針,COM 規(guī)范要求使用 Unicode 字符,并且 COM 庫中提供的 COM API 函數(shù)也使用 Unicode 字符。所以假設(shè)在組件程序內(nèi)部使用到了 ANSI 字符的話,則應(yīng)該進(jìn)行兩種字符表達(dá)的轉(zhuǎn)換。當(dāng)然,在即建立組件程序又建立客戶程序的情況下,能夠使用自己定義的參數(shù)類型,僅僅要它們與 COM 所能識別的參數(shù)類型兼容。
Visual C++ 提供兩種字符串的轉(zhuǎn)換:
namespace _com_util {
BSTR ConvertStringToBSTR(const char *pSrc) throw(_com_error);
BSTR ConvertBSTRToString(BSTR pSrc) throw(_com_error);
}
BSTR 是雙字節(jié)寬度字符串,它是最經(jīng)常使用的自己主動化數(shù)據(jù)類型。
---------------------------------------------------
接口描寫敘述語言 IDL
---------------------------------------------------
COM 規(guī)范在採用 OSF 的 DCE 規(guī)范描寫敘述遠(yuǎn)程調(diào)用接口 IDL (interface description language,接口描寫敘述語言)的基礎(chǔ)上,進(jìn)行擴(kuò)展形成了 COM 接口的描寫敘述語言。接口描寫敘述語言提供了一種不依賴于不論什么語言的接口的描寫敘述方法,因此,它能夠成為組件程序和客戶程序之間的共同語言。
COM 規(guī)范使用的 IDL 接口描寫敘述語言不僅可用于定義 COM 接口,同一時候還定義了一些經(jīng)常使用的數(shù)據(jù)類型,也能夠描寫敘述自己定義的數(shù)據(jù)結(jié)構(gòu),對于接口成員函數(shù),我們能夠定義每個參數(shù)的類型、輸入輸出特性,甚至支持可變長度的數(shù)組的描寫敘述。IDL 支持指針類型,與 C/C++ 非常相似。比如:
interface IDictionary
{
HRESULT Initialize()
HRESULT LoadLibrary([in] string);
HRESULT InsertWord([in] string, [in] string);
HRESULT DeleteWord([in] string);
HRESULT LookupWord([in] string, [out] string *);
HRESULT RestoreLibrary([in] string);
HRESULT FreeLibrary();
}
Microsoft Visual C++ 提供了 MIDL 工具,能夠把 IDL 接口描寫敘述文件編譯成 C/C++ 兼容的接口描寫敘述頭文件(.h)。
---------------------------------------------------
IUnknown 接口
---------------------------------------------------
IUnknown 的 IDL 定義:
interface IUnknown
{
HRESULT QueryInterface([in] REFIID iid, [out] void **ppv);
ULONG AddRef(void);
ULONG Release(void);
}
IUnkown 的 C++ 定義:
class IUnknown
{
virutal HRESULT _stdcall QueryInterface(const IID& iid, void **ppv) = 0;
virtual ULONG _stdcall AddRef() = 0;
virutal ULONG _stdcall Release() = 0;
}
---------------------------------------------------
COM 對象的接口原則
---------------------------------------------------
COM 規(guī)范對 QueryInterface 函數(shù)設(shè)置了以下規(guī)則:
1. 對于同一個對象的不同接口指針,查詢得到的 IUnknown 接口必須全然同樣。也就是說,每個對象的 IUnknown 接口指針是唯一的。因此,對兩個接口指針,我們能夠通過推斷其查詢到的 IUnknown 接口是否相等來推斷它們是否指向同一個對象。
2. 接口自反性。對一個接口查詢其自身總應(yīng)該成功,比方:
pIDictionary->QueryInterface(IID_Dictionary, ...) 應(yīng)該返回 S_OK。
3. 接口對稱性。假設(shè)從一個接口指針查詢到還有一個接口指針,則從第二個接口指針再回到第一個接口指針必然成功,比方:
pIDictionary->QueryInterface(IID_SpellCheck, (void **)&pISpellCheck);
假設(shè)查找成功的話,則再從 pISpellCheck 查回 IID_Dictionary 接口肯定成功。
4. 接口傳遞性。假設(shè)從第一個接口指針查詢到第二個接口指針,從第二個接口指針能夠查詢到第三個接口指針,則從第三個接口指針一定能夠查詢到第一個接口指針。
5. 接口查詢時間無關(guān)性。假設(shè)在某一個時刻能夠查詢到某一個接口指針,則以后不論什么時間再查詢同樣的接口指針,一定能夠查詢成功。
總之,無論我們從哪個接口出發(fā),我們總能夠到達(dá)不論什么一個接口,并且我們也總能夠回到最初的那個接口。
===================================================
⊙ 第三章 COM 的實現(xiàn)
===================================================
COM 組件注冊信息
---------------------------------------------------
當(dāng)前機(jī)器上全部組件的信息 HKEY_CLASS_ROOT/CLSID
進(jìn)程內(nèi)組件 HKEY_CLASS_ROOT/CLSID/guid/InprocServer32
進(jìn)程外組件 HKEY_CLASS_ROOT/CLSID/guid/LocalServer32
組件所屬類別(CATID) HKEY_CLASS_ROOT/CLSID/guid/Implemented Categories
COM 接口的配置信息 HKEY_CLASS_ROOT/Interface
代理 DLL/存根 DLL HKEY_CLASS_ROOT/CLSID/guid/ProxyStubClsid
HKEY_CLASS_ROOT/CLSID/guid/ProxyStubClsid32
類型庫的信息 HKEY_CLASS_ROOT/TypeLib
字符串命名 ProgID HKEY_CLASS_ROOT/ (比如 "COMCTL.TreeCtrl")
組件 GUID HKEY_CLASS_ROOT/COMTRL.TreeControl/CLSID
缺省版本號號 HKEY_CLASS_ROOT/COMTRL.TreeControl/CurVer
(比如 CurVer = "COMTRL.TreeCtrl.1", 那么
HKEY_CLASS_ROOT/COMTRL.TreeControl.1 也存在)
當(dāng)前機(jī)器全部組件類別 HKEY_CLASS_ROOT/Component Categories
COM 提供兩個 API 函數(shù) CLSIDFromProgID 和 ProgIDFromCLSID 轉(zhuǎn)換 ProgID 和 CLSID。
假設(shè) COM 組件支持同樣一組接口,則能夠把它們分到同一類中,一個組件能夠被分到多個類中。比方全部的自己主動化對象都支持 IDispatch 接口,則能夠把它們歸成一類“Automation Objects”。類別信息也用一個 GUID 來描寫敘述,稱為 CATID。組件類別最基本的用處在于客戶能夠高速發(fā)現(xiàn)機(jī)器上的特定類型的組件對象,否則的話,就必須檢查全部的組件對象,并把組件對象裝入到內(nèi)存中實例化,然后依次詢問是否實現(xiàn)了必要的接口,如今使用了組件類別,就能夠節(jié)省查詢過程。
---------------------------------------------------
注冊 COM 組件
---------------------------------------------------
RegSrv32.exe 用于注冊一個進(jìn)程內(nèi)組件,它調(diào)用 DLL 的 DllRegisterServer 和 DllUnregisterServer 函數(shù)完畢組件程序的注冊和注銷操作。假設(shè)操作成功返回 TRUE,否則返回 FALSE。
對于進(jìn)程外組件程序,情形稍有不同,由于它自身是個可運(yùn)行程序,并且它也不能提供入口函數(shù)供其它程序使用。因此,COM 規(guī)范中規(guī)定,支持自注冊的進(jìn)程外組件必須支持兩個命令行參數(shù) /RegServer 和 /UnregServer,以便完畢注冊和注銷操作。命令行參數(shù)大寫和小寫無關(guān),并且 “/” 能夠用 “-” 替代。假設(shè)操作成功,程序返回 0,否則,返回非 0 表示失敗。
---------------------------------------------------
類廠和 DllGetObjectClass 函數(shù)
---------------------------------------------------
類廠(class factory)是 COM 對象的生產(chǎn)基地,COM 庫通過類廠創(chuàng)建 COM 對象;對應(yīng)每個 COM 類,有一個類廠專門用于該 COM 類的對象創(chuàng)建操作。類廠本身也是一個 COM 對象,它支持一個特殊的接口 IClassFactory:
class IClassFactory : public IUnknown
{
virtual HRESULT _stdcall CreateInstance(IUnknown *pUnknownOuter,
const IID& iid, void **ppv) = 0;
virtual HRESULT _stdcall LockServer(BOOL bLock) = 0;
}
CreateInstance 成員函數(shù)用于創(chuàng)建對應(yīng)的 COM 對象。第一個參數(shù) pUnknownOuter 用于對象類被聚合的情形,一般設(shè)置為 NULL;第二個參數(shù) iid 是對象創(chuàng)建完畢后客戶應(yīng)該得到的初始接口 IID;第三個參數(shù) ppv 存放返回的接口指針。
LockServer 成員函數(shù)用于控制組件的生存周期。
類廠對象是由 DLL 引出函數(shù) DllGetClassObject 創(chuàng)建的:
HRESULT DllGetClassObject(const CLSID& clsid, const IID& iid, (void **)ppv);
DllGetClassObject 函數(shù)的第一個參數(shù)為待創(chuàng)建對象的 CLSID。由于一個組件可能實現(xiàn)了多個 COM 對象類,所以在 DllGetClassObject 函數(shù)的參數(shù)中有必要指定 CLSID,以便創(chuàng)建正確的 class factory。另兩個參數(shù) iid 和 ppv 分別指于指定接口 IID 和存放類廠接口指針。
COM 庫在接到對象創(chuàng)建的指令后,它要調(diào)用進(jìn)程內(nèi)組件的 DllGetClassObject 函數(shù),由該函數(shù)創(chuàng)建類廠對象,并返回類廠對象的接口指針。COM 庫或客戶一旦擁有類廠的接口指針,它們就能夠通過 IClassFactory 的成員函數(shù) CreateInstance 創(chuàng)建對應(yīng)的 COM 對象。
---------------------------------------------------
CoGetClassObject 函數(shù)
---------------------------------------------------
在 COM 庫中,有三個 API 可用于對象的創(chuàng)建,它們各自是 CoGetClassObject、CoCreateInstnace 和 CoCreateInstanceEx。通常情況下,客戶程序調(diào)用當(dāng)中之中的一個完畢對象的創(chuàng)建,并返回對象的初始接口指針。COM 庫與類廠也通過這三個函數(shù)進(jìn)行交互。
HRESULT CoGetClassObject(const CLSID& clsid, DWORD dwClsContext,
COSERVERINFO *pServerInfo, const IID& iid, (void **)ppv);
CoGetClassObject 函數(shù)先找到由 clsid 指定的 COM 類的類廠,然后連接到類廠對象,假設(shè)須要的話,CoGetClassObject 函數(shù)裝入組件代碼。假設(shè)是進(jìn)程內(nèi)組件對象,則 CoGetClassObject 調(diào)用 DLL 模塊的 DllGetClassObject 引出函數(shù),把參數(shù) clsid、iid 和 ppv 傳給 DllGetClassObject 函數(shù),并返回類廠對象的接口指針。通常情況下 iid 為 IClassFactory 的標(biāo)識符 IID_IClassFactory。假設(shè)類廠對象還支持其它可用于創(chuàng)建操作的接口,也能夠使用其它的接口標(biāo)識符。比如,可請求 IClassFactory2 接口,以便在創(chuàng)建時,驗證用戶的許可證情況。IClassFactory2 接口是對 IClassFactory 的擴(kuò)展,它加強(qiáng)了組件創(chuàng)建的安全性。
參數(shù) dwClsContext 指定組件類別,能夠指定為進(jìn)程內(nèi)組件、進(jìn)程外組件或者進(jìn)程內(nèi)控制對象(相似于進(jìn)程外組件的代理對象,主要用于 OLE 技術(shù))。參數(shù) iid 和 ppv 分別對應(yīng)于 DllGetClassObject 的參數(shù),用于指定接口 IID 和存放類對象的接口指針。參數(shù) pServerInfo 用于創(chuàng)建遠(yuǎn)程對象時指定server信息,在創(chuàng)建進(jìn)程內(nèi)組件對象或者本地進(jìn)程外組件時,設(shè)置 NULL。
假設(shè) CoGetClassObject 函數(shù)創(chuàng)建的類廠對象位于進(jìn)程外組件,則情形要復(fù)雜得多。首先 CoGetClassObject 函數(shù)啟動組件進(jìn)程,然后一直等待,直到組件進(jìn)程把它支持的 COM 類對象的類廠注冊到 COM 中。于是 CoGetClassObject 函數(shù)把 COM 中對應(yīng)的類廠信息返回。因此,組件外進(jìn)程被 COM 庫啟動時(帶命令行參數(shù)“/Embedding”),它必須把所支持的 COM 類的類廠對象通過 CoRegisterClassObject 函數(shù)注冊到 COM 中,以便 COM 庫創(chuàng)建 COM 對象使用。當(dāng)進(jìn)程退出時,必須調(diào)用 CoRevokeClassObject 函數(shù)以便通知 COM 它所注冊的類廠對象不再有效。組件程序調(diào)用 CoRegisterClassObject 函數(shù)和 CoRevokeClassObject 函數(shù)必須配對,以保證 COM 信息的一致性。
---------------------------------------------------
CoCreateInstance / CoCreateInstanceEx 函數(shù)
---------------------------------------------------
HRESULT CoCreateInstance(const CLSID& clsid, IUnknown *pUnknownOuter,
DWORD dwClsContext, const IID& iid, (void **)ppv);
CoCreateInstance 是一個被包裝過的輔助函數(shù),在它的內(nèi)部實際上也調(diào)用了 CoGetClassObject 函數(shù)。CoCreateInstance 的參數(shù) clsid 和 dwClsContext 的含義與 CoGetClassObject 對應(yīng)的參數(shù)一致,(CoCreateInstance 的 iid 和 ppv 參數(shù)與 CoGetClassObject 不同,一個是表示對象的接口信息,一個是表示類廠的接口信息)。參數(shù) pUnknownOuter 與類廠接口的 CreateInstance 中對應(yīng)的參數(shù)一致,主要用于對象被聚合的情況。CoCreateInstance 函數(shù)把通過類廠創(chuàng)建對象的過程封裝起來,客戶程序僅僅要指定對象類的 CLSID 和待輸出的接口指針及接口 ID,客戶程序能夠不與類廠打交道。CoCreateInstance 能夠用以下的代碼實現(xiàn):
(savetime 注:以下代碼中 ppv 指針的應(yīng)用,好像應(yīng)該是 void **)
HRESULT CoCreateInstance(const CLSID& clsid, IUnknown *pUnknownOuter,
DWORD dwClsContext, const IID& iid, void *ppv)
{
IClassFactory *pCF;
HRESULT hr;
hr = CoGetClassObject(clsid, dwClsContext, NULL, IID_IClassFactory,
(void *) pCF);
if (FAILED(hr)) return hr;
hr = pCF->CreateInstance(pUnknownOuter, iid, (void *)ppv);
pFC->Release();
return hr;
}
從這段代碼我們能夠看出,CoCreateInstance 函數(shù)首先利用 CoGetClassObject 函數(shù)創(chuàng)建類廠對象,然后用得到的類廠對象的接口指針創(chuàng)建真正的 COM 對象,最后把類廠對象釋放掉并返回,這樣就把類廠屏蔽起來。
可是,用 CoCreateInstance 并不能創(chuàng)建遠(yuǎn)程機(jī)器上的對象,由于在調(diào)用 CoGetClassObject 時,把第三個用于指定server信息的參數(shù)設(shè)置為 NULL。假設(shè)要創(chuàng)建遠(yuǎn)程對象,能夠使用 CoCreateInstance 的擴(kuò)展函數(shù) CoCreateInstanceEx:
HRESULT CoCreateInstanceEx(const CLSID& clsid, IUnknown *pUnknownOuter,
DWORD dwClsContext, COSERVERINFO *pServerInfo, DWORD dwCount,
MULTI_QI *rgMultiQI);
前三個參數(shù)與 CoCreateInstance 一樣,pServerInfo 與 CoGetClassOjbect 的參數(shù)一樣,用于指定server信息,最后兩個參數(shù) dwCount 和 rgMultiQI 指定了一個結(jié)構(gòu)數(shù)組,能夠用于保存多個對象接口指針,其目的在于一次獲得多個接口指針,以便降低客戶程序與組件程序之間的頻繁交互,這對于網(wǎng)絡(luò)環(huán)境下的遠(yuǎn)程對象是非常有意義的。
---------------------------------------------------
COM 庫的初始化
---------------------------------------------------
調(diào)用 COM 庫的函數(shù)之前,為了使函數(shù)有效,必須調(diào)用 COM 庫的初始化函數(shù):
HRESULT CoInitialize(IMalloc *pMalloc);
pMalloc 用于指定一個內(nèi)存分配器,可由應(yīng)用程序指定內(nèi)存分配原則。普通情況下,我們直接把參數(shù)設(shè)為 NULL,則 COM 庫將使用缺省提供的內(nèi)存分配器。
返回值:S_OK 表示初始化成功
S_FALSE 表示初始化成功,但這次調(diào)用不是本進(jìn)程中首次調(diào)用初始化函數(shù)
S_UNEXPECTED 表示初始化過程中發(fā)生了錯誤,應(yīng)用程序不能使用 COM 庫
通常,一個進(jìn)程對 COM 庫僅僅進(jìn)行一次初始化,并且,在同一個模塊單元中對 COM 庫進(jìn)行多次初始化并沒有意義。唯一不須要初始化 COM 庫的函數(shù)是獲取 COM 庫版本號的函數(shù):
DWORD CoBuildVersion();
返回值:高 16 位 主版本號號
低 16 位 次版本號號
COM 程序在用完 COM 庫服務(wù)之后,一般是在程序退出之前,一定要調(diào)用終止 COM 庫服務(wù)函數(shù),以便釋放 COM 庫所維護(hù)的資源:
void CoUninitialize(void);
注意:凡是調(diào)用 CoInitialize 函數(shù)返回 S_OK 的進(jìn)程或程序模塊一定要有對應(yīng)的 CoUninitialize 函數(shù)調(diào)用,以保證 COM 庫有效地利用資源。
(? 假設(shè)在一個模塊中調(diào)用 CoInitialize 返回 S_OK,那么它調(diào)用 CoUnitialize 函數(shù)后,其它也在使用 COM 庫的模塊是否會出錯誤?還是 COM 庫會自己主動檢查有哪些模塊在使用?)
---------------------------------------------------
COM 庫的內(nèi)存管理
---------------------------------------------------
由于 COM 組件程序和客戶程序是通過二進(jìn)制級標(biāo)準(zhǔn)建立連接的,所以在 COM 應(yīng)用程序中凡是涉及客戶、COM 庫和組件三者之間內(nèi)存交互(分配和釋放不在同一個模塊中)的操作必須使用一致的內(nèi)存管理器。COM 提供的內(nèi)存管理標(biāo)準(zhǔn),實際上是一個 IMalloc 接口:
// IID_IMalloc: {00000002-0000-0000-C000-000000000046}
class IMalloc: public IUnknown
{
void * Alloc(ULONG cb) = 0;
void * Realloc(void *pv, ULONG cb) = 0;
void Free(void *pv) = 0;
ULONG GetSize(void *pv) = 0; // 返回分配的內(nèi)存大小
int DidAlloc(void *pv) = 0; // 確定內(nèi)存指針是否由該內(nèi)存管理器分配
void HeapMinimize() = 0; // 使堆內(nèi)存盡可能降低,把沒用到的內(nèi)存還給
// 操作系統(tǒng),用于性能優(yōu)化
}
獲得 IMalloc 接口指針:
HRESULT CoGetMalloc(DWORD dwMemContext, IMalloc **ppMalloc);
CoGetMalloc 函數(shù)的第一個參數(shù) dwMemContext 用于指定內(nèi)存管理器的類型。COM 庫中包括兩種內(nèi)存管理器,一種就是在初始化時指定的內(nèi)存管理器或者其內(nèi)部缺省的管理器,也稱為作業(yè)管理器(task allocator),這樣的管理器在本進(jìn)程內(nèi)有效,要獲取該管理器,在 dwMemContext 參數(shù)中指定為 MEMCTX_TASK;還有一種是跨進(jìn)程的共享分配器,由 OLE 系統(tǒng)提供,要獲取這樣的管理器,dwMemContext 參數(shù)中指定為 MEMCTX_SHARED,使用共享管理器的便利是,能夠在一個進(jìn)程內(nèi)分配內(nèi)存并傳給第二個進(jìn)程,在第二個進(jìn)程內(nèi)使用此內(nèi)存甚至釋放掉此內(nèi)存。
僅僅要函數(shù)的返回值為 S_OK,則 ppMalloc 就指向了 COM 庫的內(nèi)存管理器接口指針,能夠使用它進(jìn)行內(nèi)存操作,使用完畢后,應(yīng)該調(diào)用 Release 成員函數(shù)釋放控制權(quán)。
COM 庫封裝了三個 API 函數(shù),可用于內(nèi)存分配和釋放:
void * CoTaskMemAlloc(ULONG cb);
void CoTaskFree(void *pv);
void CoTaskMemRealloc(void *pv, ULONG cb);
這三個函數(shù)分配對應(yīng)于 IMalloc 的三個成員函數(shù):Alloc、Realloc 和 Free。
例:COM 程序怎樣從 CLSID 值找到對應(yīng)的 ProgID 值:
WCHAR *pwProgID;
char pszProgID[128];
hResult = ::ProgIDFromCLSID(CLSID_Dictionary, &pwProgID);
if (hResult != S_OK) {
...
}
wcstombs(pszProgID, pwProgID, 128);
CoTaskMemFree(pwProgID); // 注意:必須釋放內(nèi)存
在調(diào)用 COM 函數(shù) ProgIDFromCLSID 返回之后,由于 COM 庫為輸出變量 pwProgID 分配了內(nèi)存空間,所以應(yīng)用程序在用完 pwProgID 變量之后,一定要調(diào)用 CoTaskMemFree 函數(shù)釋放內(nèi)存。該樣例說明了在 COM 庫中分配內(nèi)存,而在調(diào)用程序中釋放內(nèi)存的一種情況。COM 庫中其它一些函數(shù)也有相似的特性,尤其是一些包括不定長度輸出參數(shù)的函數(shù)。
---------------------------------------------------
組件程序的裝載和卸載
---------------------------------------------------
進(jìn)程內(nèi)組件的裝載:
客戶程序調(diào)用COM 庫的 CoCreateInstance 或 CoGetClassObject 函數(shù)創(chuàng)建 COM 對象,在 CoGetClassObject 函數(shù)中,COM 庫依據(jù)系統(tǒng)注冊表中的信息,找到類標(biāo)識符 CLSID 對應(yīng)的組件程序(DLL 文件)的全路徑,然后調(diào)用 LoadLibrary(實際上是 CoLoadLibrary)函數(shù),并調(diào)用組件程序的 DllGetClassObject 引出函數(shù)。DllGetClassObject 函數(shù)創(chuàng)建對應(yīng)的類廠對象,并返回類廠對象的 IClassFactory 接口。至此 CoGetClassObject 函數(shù)的任務(wù)完畢,然后客戶程序或者 CoCreateInstance 函數(shù)繼續(xù)調(diào)用類廠對象的 CreateInstance 成員函數(shù),由它負(fù)責(zé) COM 對象的創(chuàng)建工作。
CoCreateInstance
|-CoGetClassObject
|-Get CLSID -> DLLfile path
|-CoLoadLibrary
|-DLLfile.DllGetClassObject
|-return IClassFactory
|-IClassFactory.CreateInstnace
進(jìn)程外組件的裝載:
在 COM 庫的 CoGetClassObject 函數(shù)中,當(dāng)它發(fā)現(xiàn)組件程序是 EXE 文件(由注冊表組件對象信息中的 LocalServer 或 LocalServer32 值指定)時,COM 庫創(chuàng)建一個進(jìn)程啟動組件程序,并帶上“/Embedding”命令行參數(shù),然后等待組件程序;而組件程序在啟動后,當(dāng)它檢查到“/Embedding”命令行參數(shù)后,就會創(chuàng)建類廠對象,然后調(diào)用 CoRegisterClassObject 函數(shù)把類廠對象注冊到 COM 中。當(dāng) COM 庫檢查到組件對象的類廠之后,CoGetClassObject 函數(shù)就把類廠對象返回。由于類廠與客戶程序運(yùn)行在不同的進(jìn)程中,所以客戶程序得到的是類廠的代理對象。一旦客戶程序或 COM 庫得到了類廠對象,它就能夠完畢組件對象的創(chuàng)建工作。
進(jìn)程內(nèi)對象和進(jìn)程外對象的不同創(chuàng)建過程僅僅影響了 CoGetClassObject 函數(shù)的實現(xiàn)過程,對于客戶程序來說是全然透明的。
CoGetClassObject
|-LocalServer/LocalServer32
|-Execute EXE /Embedding
|-Create class factory
|-CoRegisterClassObject ( class factory )
|-return class factory (proxy)
進(jìn)程內(nèi)組件的卸載:
僅僅有當(dāng)組件程序滿足了兩個條件時,它才干被卸載,這兩個條件是:組件中對象數(shù)為 0,類廠的鎖計數(shù)為 0。滿足這兩個條件時,DllCanUnloadNow 引出函數(shù)返回 TRUE。COM 提供了一個函數(shù) CoFreeUnusedLibraries,它會檢測當(dāng)前進(jìn)程中的全部組件程序,當(dāng)發(fā)現(xiàn)某個組件程序的 DllCanUnloadNow 函數(shù)返回 TRUE 時,就調(diào)用 FreeLibrary 函數(shù)(實際上是 CoFreeLibrary 函數(shù))把該組件從程序從內(nèi)存中卸出。
該由誰來調(diào)用 CoFreeUnusedLibraries 函數(shù)呢?由于在組件程序運(yùn)行過程中,它不可能把自己從內(nèi)存中卸出,所以這個任務(wù)應(yīng)該由客戶來完畢。客戶程序隨時都能夠調(diào)用 CoFreeUnusedLibraries 函數(shù)完畢卸出工作,但通常的做法是,在程序的空暇處理過程中調(diào)用 CoFreeUnusedLibraries 函數(shù),這樣做既能夠避免程序中處處考慮對 CoFreeUnusedLibraries 函數(shù)的調(diào)用,又能夠使不再使用的組件程序得到及時清除,提高資源的利用率,COM 規(guī)范也推薦這樣的做法。
進(jìn)程外組件的卸載:
進(jìn)程外組件的卸載比較簡單,由于組件程序運(yùn)行在單獨(dú)的進(jìn)程中,一旦其退出的條件滿足,它僅僅要從進(jìn)程的主控函數(shù)返回就可以。在 Windows 系統(tǒng)中,進(jìn)程的主控函數(shù)為 WinMain。
前面曾經(jīng)說過,在組件程序啟動運(yùn)行時,它調(diào)用 CoRegisterClassObject 函數(shù),把類廠對象注冊到 COM 中,注冊之后,類廠對象的引用計數(shù)始終大于 0,因此單憑類廠對象的引用計數(shù)無法控制進(jìn)程的生存期,這也是引入類廠對象的加鎖和減鎖操作的原因。進(jìn)程外組件的載條件與 DllCanUnloadNow 中的推斷相似,也須要推斷 COM 對象是否還存在、以及推斷是否鎖計數(shù)器為 0,僅僅有當(dāng)條件滿足了,進(jìn)程的主函數(shù)才干夠退出。
從原則上講,進(jìn)程外組件程序的卸載就是這么簡單,但實際上情況可能復(fù)雜一些,由于有些組件程序在運(yùn)行過程中能夠創(chuàng)建自己的對象,或者包括用戶界面的程序在運(yùn)行過程中,用戶手工關(guān)閉了進(jìn)程,那么進(jìn)程對這些動作的處理要復(fù)雜一些。比如,組件程序在運(yùn)行過程中,用戶又打開了一個文件并進(jìn)行操作,那么即使原先創(chuàng)建的對象被釋放了,并且鎖計數(shù)器也為 0,進(jìn)程也不能退出,它必須繼續(xù)為用戶服務(wù),就像是用戶打開的進(jìn)程一樣。對這樣的程序,能夠增加一個“用戶控制”標(biāo)記 flag,假設(shè) flag 為 FALSE,則能夠按簡單的方法直接退出程序就可以;假設(shè) flag 為 TRUE,則表明用戶參與了控制,組件進(jìn)程不能立即退出,但應(yīng)該調(diào)用 CoRevokeClassObject 函數(shù)以便與 CoRegisterClassObject 調(diào)用相響呼應(yīng),把進(jìn)程留給用戶繼續(xù)進(jìn)行。
假設(shè)組件程序在運(yùn)行過程中,用戶要關(guān)閉進(jìn)程,而此時并不滿足進(jìn)程退出條件,那么進(jìn)程能夠採取兩種辦法:第一種方法,把應(yīng)用隱藏起來,并把 flag 標(biāo)記設(shè)置為 FALSE,然后組件程序繼續(xù)運(yùn)行直到卸載條件滿足為止;還有一種辦法是,調(diào)用 CoDisconnectObject 函數(shù),強(qiáng)迫脫離對象與客戶之間的關(guān)系,并強(qiáng)行終止進(jìn)程,這樣的方法比較粗暴,不提倡採用,但不得已時能夠也使用,以保證系統(tǒng)完畢一些高優(yōu)先級的操作。
---------------------------------------------------
COM 庫經(jīng)常使用函數(shù)
---------------------------------------------------
初始化函數(shù) CoBuildVersion 獲得 COM 庫的版本號號
CoInitialize COM 庫初始化
CoUninitialize COM 庫功能服務(wù)終止
CoFreeUnusedLibraries 釋放進(jìn)程中全部不再使用的組件程序
GUID 相關(guān)函數(shù) IsEqualGUID 推斷兩個 GUID 是否相等
IsEqualIID 推斷兩個 IID 是否相等
IsEqualCLSID 推斷兩個 CLSID 是否相等 (*為什么要3個函數(shù))
CLSIDFromProgID 字符串組件標(biāo)識轉(zhuǎn)換為 CLSID 形式
StringFromCLSID CLSID 形式標(biāo)識轉(zhuǎn)化為字符串形式
IIDFromString 字符串轉(zhuǎn)換為 IID 形式
StringFromIID IID 形式轉(zhuǎn)換為字符串
StringFromGUID2 GUID 形式轉(zhuǎn)換為字符串(*為什么有 2)
對象創(chuàng)建函數(shù) CoGetClassObject 獲取類廠對象
CoCreateInstance 創(chuàng)建 COM 對象
CoCreateInstanceEx 創(chuàng)建 COM 對象,可指定多個接口或遠(yuǎn)程對象
CoRegisterClassObject 登記一個對象,使其它應(yīng)用程序能夠連接到它
CoRevokeClassObject 取消對象的登記
CoDisconnectObject 斷開其它應(yīng)用與對象的連接
內(nèi)存管理函數(shù) CoTaskMemAlloc 內(nèi)存分配函數(shù)
CoTaskMemRealloc 內(nèi)存又一次分配函數(shù)
CoTaskMemFree 內(nèi)存釋放函數(shù)
CoGetMalloc 獲取 COM 庫內(nèi)存管理器接口
---------------------------------------------------
HRESULT 類型
---------------------------------------------------
大多數(shù) COM 函數(shù)以及一些接口成員函數(shù)的返回值類型均為 HRESULT 類型。HRESULT 類型的返回值反映了函數(shù)中的一些情況,其類型定義規(guī)范例如以下:
31 30 29 28 16 15 0
|-----|--|------------------------|-----------------------------------|
類別碼 (30-31) 反映函數(shù)調(diào)用結(jié)果:
00 調(diào)用成功
01 包括一些信息
10 警告
11 錯誤
自己定義標(biāo)記(29) 反映結(jié)果是否為自己定義標(biāo)識,1 為是,0 則不是;
操作碼 (16-28) 標(biāo)識結(jié)果操作來源,在 Windows 平臺上,其定義例如以下:
#define FACILITY_WINDOWS 8
#define FACILITY_STORAGE 3
#define FACILITY_RPC 1
#define FACILITY_SSPI 9
#define FACILITY_WIN32 7
#define FACILITY_CONTROL 10
#define FACILITY_NULL 0
#define FACILITY_INTERNET 12
#define FACILITY_ITF 4
#define FACILITY_DISPATCH 2
#define FACILITY_CERT 11
操作結(jié)果碼(0-15) 反映操作的狀態(tài),WinError.h 定義了 Win32 函數(shù)全部可能返回結(jié)果。
以下是一些經(jīng)經(jīng)常使用到的返回值和宏定義:
S_OK 函數(shù)運(yùn)行成功,其值為 0 (注意,其值與 TRUE 相反)
S_FALSE 函數(shù)運(yùn)行成功,其值為 1
S_FAIL 函數(shù)運(yùn)行失敗,失敗原因不確定
E_OUTOFMEMORY 函數(shù)運(yùn)行失敗,失敗原由于內(nèi)存分配不成功
E_NOTIMPL 函數(shù)運(yùn)行失敗,成員函數(shù)沒有被實現(xiàn)
E_NOTINTERFACE 函數(shù)運(yùn)行失敗,組件沒有實現(xiàn)指定的接口
不能簡單地把返回值與 S_OK 和 S_FALSE 比較,而要用 SECCEEDED 和 FAILED 宏進(jìn)行推斷。
===================================================
⊙ 第四章 COM 特性
===================================================
可重用性:包容和聚合
---------------------------------------------------
包容模型:
組件對象在接口的實現(xiàn)代碼中運(yùn)行自身創(chuàng)建的還有一個組件對象的接口函數(shù)(客戶/server模型)。這個對象同一時候?qū)崿F(xiàn)了兩個(或很多其它)接口的代碼。
聚合模型:
組件對象在接口的查詢代碼中把接口傳遞給自已創(chuàng)建的還有一個對象的接口查詢函數(shù),而不實現(xiàn)該接口的代碼。還有一個對象必須實現(xiàn)聚合模型(也就是說,它知道自己正在被還有一個組件對象聚合),以便 QueryInterface 函數(shù)能夠正常運(yùn)作。
在組件對象被聚合的情況下,當(dāng)客戶請求它所不支持的接口或者請求 IUnknown 接口時,它必須把控制交給外部對象,由外部對象決定客戶程序的請求結(jié)果。
聚合模型體現(xiàn)了組件軟件真正意義上的重用。
聚合模型實現(xiàn)的關(guān)鍵在 CoCreateInstance 函數(shù)和 IClassFactory 接口:
HRESULT CoCreateInstance(const CLSID& clsid, IUnknown *pUnknownOuter,
DWORD dwClsContext, const IID& iid, (void **)ppv);
// class IClassFactory : public IUnknown
virtual HRESULT _stdcall CreateInstance(IUnknown *pUnknownOuter,
const IID& iid, void **ppv) = 0;
當(dāng)中 pUnknownOuter 參數(shù)用于指定組件對象是否被聚合。假設(shè) pUnknownOuter 參數(shù)為 NULL,說明組件對象正常使用,否則說明被聚合使用,pUnknownOuter 是外部組件對象的接口指針。
聚合模型下的被聚合對象的引用計數(shù)成員函數(shù)也要進(jìn)行特別處理。在未被聚合的情況下,能夠使用一般的引用計數(shù)方法。在被聚合時,由客戶調(diào)用 AddRef/Release 函數(shù)時,必須轉(zhuǎn)向外部組件對象的 AddRef/Release 方法。這時,外部組件對象要控制被聚合的對象必須採用其它的引用計數(shù)接口。
---------------------------------------------------
進(jìn)程透明性 (待學(xué))
安全性(待學(xué))
多線程特性(待學(xué))
---------------------------------------------------
===================================================
⊙ 第五章 用 Visual C++ 開發(fā) COM 應(yīng)用
===================================================
Win32 SDK 提供的一些頭文件的說明
---------------------------------------------------
Unknwn.h 標(biāo)準(zhǔn)接口 IUnknown 和 IClassFacatory 的 IID 及接口成員函數(shù)的定義
Wtypes.h 包括 COM 使用的數(shù)據(jù)結(jié)構(gòu)的說明
Objidl.h 全部標(biāo)準(zhǔn)接口的定義,就可以用于 C 語言風(fēng)格的定義,也可用于 C++ 語言 Comdef.h 全部標(biāo)準(zhǔn)接口以及 COM 和 OLE 內(nèi)部對象的 CLSID ObjBase.h 全部的 COM API 函數(shù)的說明 Ole2.h 全部經(jīng)過封裝的 OLE 輔助函數(shù)
---------------------------------------------------
與 COM 接口有關(guān)的一些宏
---------------------------------------------------
DECLARE_INTERFACE(iface)
聲明接口 iface,它不從其它的接口派生
DECLARE_INTERFACE_(iface, baseiface)
聲明接口 iface,它從接口 baseiface 派生
STDMETHOD(method)
聲明接口成員函數(shù) method,函數(shù)返回類型為 HRESULT
STDMETHOD_(type, method) 聲明接口成員函數(shù) method,函數(shù)返回類型為 type
===================================================
⊙ 結(jié) 束
===================================================
轉(zhuǎn)載于:https://www.cnblogs.com/bhlsheji/p/4065439.html
總結(jié)
以上是生活随笔為你收集整理的Delphi 2007体验!的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Ubuntu上安装gtk2.0不能安装的
- 下一篇: 关于网站域名的配置过程