天天看點

Delphi 2007體驗!

<b>Delphi 2007</b><b>體驗!</b><b></b>

baidu

内容摘要:CodeGear(From Borland) 公司公布了最新的Delphi 2007 For Win32版本号。作為一個 Delphi

的使用者,第一時間下載下傳、安裝并進行了體驗,現将一些使用感受記錄例如以下

CodeGear(From Borland) 公司公布了最新的Delphi 2007 For Win32版本号。作為一個 Delphi

的使用者,第一時間下載下傳、安裝并進行了體驗,現将一些使用感受記錄例如以下:

注:以下的對照主要是針對Delphi7與Delphi2007,以下列出的部分功能可能在Delphi8/2005/2006中已存在。

1、下載下傳

可在CodeGear官方站點下載下傳試用版,或在VeryCD上尋找ISO,整個安裝檔案約1.2G多一些。

2、安裝

Delphi

2007放棄了InstallShield,採用了InstallAware安裝軟體,整個安裝操作非常友好。在安裝結束時,會提示是否在Delphi啟

動時自己主動檢查更新,建議不要選擇此項,由于安裝完畢後無法禁用。Delphi 2007在開始菜單中建立了自己主動檢查更新的快捷方式。

3、啟動

Delphi 2007的啟動速度沒有傳說中那麼快,但和Delphi7相比也差不了多少,整體感覺不錯。

4、界面

Delphi 2007的界面和之前的BDS 8/2005/2006界面風格是一樣的,個人感覺不是太好,由于整個IDE顔色偏暗,Visual Studio

2005那種白亮色的界面應該更好一些。Splash和Welcome Page作的還是那麼粗糙,CodeGear應該盡快找個好美工啊。

5、速度

IDE啟動速度還不錯,IDE的反映速度也非常好,尤其是程式的編譯速度,個人感覺比Delphi7還快。

6、傳回傳統界面

習 慣了Delphi7及之前版本号的界面,對Delphi2007的這樣的一體式界面多少有些難以适應,尤其是視窗設計器。盡管能夠通過更改Desktop

Layout為Classic Undocked讓其與Delphi7有些相像,但卻失去了Delphi7的那種自由設計的效果。

在Delphi2007中,更改一個選項,可讓IDE的視窗設計器傳回Delphi的傳統風格:Tools–Options–VCL

Designer,取消選中Embedded Designer。

此選項僅僅有在IDE重新啟動後才會生效,生效後整個界面和Delphi7差點兒相同,但元件面闆無法達到傳統界面的效果。但此設計似乎有BUG,當IDE最小化的時候,視窗設計器卻沒有一同最小化。

7、新增屬性:Application.MainFormOnTaskBar

用Delphi2007建立一project,然後檢視project檔案的源碼,發現多一行代碼:

Application.MainFormOnTaskBar := True;

Delphi2007預設已将MainForm顯示于工作列,而不是之前版本号的Application。這個功能在曾經非常多Delphier都讨論過,如今Delphi自身支援了。設計此屬性非常明顯,因該是為了相容Windows

Vista。

當然工作列的右鍵菜單也發生了變化:

(Delphi 7 工作列右鍵菜單)

(Delphi 2007 工作列右鍵菜單)

8、新Project Option: Enable Runtime Themes

該project選項預設啟用,用Delphi

2007編寫的程式預設将啟用Themes,這是一個非常好的功能,曾經必須用元件:Win32-XPManifest。

Delphi 2007 IDE本身、視窗設計器已支援作業系統Themes。

9、TeeChart更新為了TeeChart Standard 7.10

TeeChart最終更新了新版本号。

10、報表元件

Delphi 2007似乎沒有附帶不論什麼報表元件,QuickReport和Rave消失了。

11、DBExpress

DBExpress重大更新至v4,架構已重寫,使用此技術的Delphier能夠試試,本人非常少使用。

12、模态視窗下的視窗閃動

在目前視窗用ShowModal顯示一個模态視窗後,再次點選目前視窗,此時顯示出的模态視窗會閃動,Delphi

2007編譯的程式最終已能實作此效果,這也是Windows程式的标準效果。

13、Project Clean 功能

在Project Manager中右鍵點選project名稱,選擇Clean,會自己主動清除project的全部暫時檔案和dcu檔案。

14、實用的快捷鍵

最終為Build Project和Run Without Dedugging功能設定了快捷鍵。

15、比Delphi7超強的編輯器

Delphi2007的編輯器功能強大,這也應該是放棄Delphi7的重要理由,如輸入Begin,自己主動生成End,代碼重構,文法實時檢查,顯示行号等。

隻是Delphi2007的那個代碼幫助提示資訊的視窗真是太醜了。

16、視窗設計器控件感應對齊

視窗設計器中的控件能夠感應對齊,相當好的功能啊。

17、中文變量名

如今的Delphi已經支援中文變量名了,你能夠試試。

18、新的元件

TTrayIcon、TFlowPanel、TGridPanel三個控件非常實用。Delphi2007新增Vista

Dialogs元件,這些元件效果非常好,可是基于Vista API的,是以僅僅能在Windows Vista下使用。

19、TLabel控件可在内容超出範圍時顯示省略号

此功能非常實用,尤其是在Label中顯示一個檔案路徑時,設定TLabel的EllipsisPosition屬性就可以。

20、新增了一些實用的屬性

Delphi2007對大多數正常元件增加了一些實用的屬性,如Margins、Padding、TForm.PopupMode等,細緻檢視一些控件的屬性清單,你就會發現非常多陌生的屬性,但他們确實都非常實用。

用了Delphi 2007一段時間,但也發現一些小問題:

1、在Project Manager中更改PAS檔案的名稱後,不自己主動更新uses清單中的名稱,呵呵~,這個要求不知道過隻是分。

2、TMainMenu元件在視窗設計器中不可預覽。

3、編輯器錯誤提示功能會誤報,如Application.MainFormOnTaskBar屬性,有時會提示不存在該屬性。

4、新安裝的元件,不但要在Tool–Options中設定Library Path,還必須設定Browseing Path,元件才幹被正常使用。

用了N年的Delphi7了,體驗了Delphi2007後覺得真應該換換了,綜合來看,Delphi2007是一個非常好的版本号,IDE速度及功能性各方面都已經非常優秀,你準備使用Delphi2007嗎?

<b>Delphi 2007 </b><b>初步印象</b><b></b>

内容摘要:經過苦苦的等待,最終等來了新一代 Delphi 2007 的下載下傳連結。昨天從 emule

上下載下傳時,發覺有非常多的人在下載下傳,讓我非常感動。原來和我一樣,對 Delphi

關注的人還不少啊。速度還算快,到夜裡就下完了。接着開始了2個小時的試用,盡管不怎麼細緻,但從 Delphi 1

一路用過來的我,對裡面的變化還比較敏感的。

經過苦苦的等待,最終等來了新一代 Delphi 2007 的下載下傳連結。昨天從 emule

相比 Delphi 2006 來說,我覺得這個版本号的 Delphi 2007

是個超強的優化版,功能方面,僅僅有少些改變。說她是優化版,那是由于她的啟動速度,編譯效率,IDE

速度是相當的快(PS:我的電腦是PM1.4G,512MB)。李維先生所說的比 Delphi 7 快一點也不為過。近期用 VS2005,打開一個 C++

控制台程式,要經過相當長的時間,編譯就更不用說了。Welcome Page 裡的“Where developers matter”真是讓人感動啊。

至于其它方面的改進,對我來說,并不是非常有吸引力。Help 系統改用 MSDN 的那套最新的 help,支援了VISTA,可是我的電腦是不能跑了。對

Together 的內建,這個非常實用。控件多了幾個。IntraWeb 變成了 CodeGear 的 VCL

部分了,但卻不支援調試,這點有些奇怪。資料庫接口統一了。特别的一點就是,原來 Borland 的辨別,如今全變成了 CodeGear。

事實上,我并不了解多少開發,更不知道開發者究竟須要 Delphi 2007 增加些什麼功能。我中心一直覺得 Delhpi

發展到今天,變化的可能已經越來越少。就像 VC 一樣,非常少變動。Win32 RAD 的開發王者,應該還是屬于

Delphi(C++上的RAD工具非常少,即便有,也是對語言進行了一些惡心擴充),速度,效率,誰能相比。如今唯一的希望是 Delphi 2007

的語言再加強一些,如 template。近期用 C++,一直在學習 template,是以也希望 Delphi 能跟上時代。

内容摘要:本文介紹delph i2007學習筆記

如今學的是delphi 的類,原D7的類我不就不記了,記下與D7不同的地方

a.class abstract 純虛類,不能執行個體化的類

type

TAbstractClass = class abstract

procedure SomeProcedure;

end;

曾經的做法是在 procedure 的後面與 abstract ,如今僅僅移類的說明上,僅僅是意思一樣,就是直覺點少打字 呵呵.

b.class sealed 這個我眼下不知是什麼意思,可能是不能繼承的類

TAbstractClass = class sealed

c.class const 類的常量,這個地方在D7内能夠定類的方法一樣能實作

TClassWithConstant = class

public

const SomeConst =

‘This is a class constant‘;

procedure

TForm1.FormCreate(Sender: TObject);

begin

ShowMessage(TClassWithConstant.SomeConst); //引用時,僅僅寫類名就可能引用,不必執行個體化

d.class type 類的類型, 在類的層次下能夠定record,class子類什麼的,這個将資料的集中展現....

TClassWithClassType = class

private

TRecordWithinAClass = record

SomeField: string;

class var

RecordWithinAClass: TRecordWithinAClass;

...

procedure TForm1.FormCreate(Sender: TObject);

TClassWithClassType.RecordWithinAClass.SomeField := ‘This is a

field of a class type declaration‘;

ShowMessage(TClassWithClassType.RecordWithinAClass.SomeField);

TOuterClass = class

strict private

MyField:

Integer;

TInnerClass = class

MyInnerField: Integer;

procedure InnerProc;

procedure OuterProc;

TOuterClass.TInnerClass.InnerProc;

<b>讓人期待的</b><b>Delphi 2007 for Win32</b>

内容摘要:從李維的Blog和一些網友的回報來看,D2007确實是比較讓人期待的一個版本号。作為Delphi的忠實支援者,我期待着Delphi的逐漸回歸,希望她能重登昔日的王者地位!

自從D7之後,Delphi似乎開始走了下坡路,到D2005時,讓非常多人感到了失望,而D2006也是非常不easy才挽回了一點局面。大家都知道如今Delphi跟Borland分家,歸屬于CodeGear繼續發展了。隻是分出去或許會是好事,這能讓一幫人更為專注地發展IDE的技術。Delphi

2007 for Win32的公布會(包括Delphi for PHP和InterBase

2007)前幾天正在密鑼緊鼓地進行着,從李維的Blog和一些網友的回報來看,D2007确實是比較讓人期待的一個版本号。作為Delphi的忠實支援者,我期待着Delphi的逐漸回歸,希望她能重登昔日的王者地位!(這次不要當VB殺手了,來當當C#和Java殺手吧....

XD)

Delphi2007 for Win32一些技術亮點:

1、IDE工具採用.Net2.0來編寫,安裝時候須要.Net FrameWork2.0,但編譯出來的Exe是純正的Win32程式,公布不須要.Net

FrameWork2.0。新IDE採用.Net

FrameWork2.0是為了更好的統一IDE平台,利用.Net的反射、泛型等進階特性,節省編寫IDE時間。新的IDE确實比D7啟動還要快,大概是五六秒時間左右就啟動,跟VS.Net2005差點兒相同。示範中,新的IDE編譯建立project比D7還要快一點。

2、Delphi2007支援Vista界面,封裝了Vsita的新API函數。

3、Delphi2007有一個功能非常有意思,支援D2006的全部BPL元件。意思是,假設你用D2006編譯過的BPL,沒有源碼和DCU,也能夠直接安裝在Delphi2007。哈哈,這個是delphi版本号上的史無前例,即使第三方控件包來不及支援Delphi2007,也最好還是礙開發者從D2006更新到Delphi2007做項目開發。

4、支援MS

Build。比方支援Debug、Release。也支援編譯前和編譯後事件,可調用BAT檔案。比方,你編譯前郵件告訴老闆,你要給我加薪50%,否則十分鐘後銷毀源碼。另外,也能夠改動其XML格式,僅僅編譯特定的程式代碼。

5、Delphi2007 for

Win32資料集控件全面支援Unicode。比方,你的資料庫表字段可採用中文名稱作字段。Filter也支援Unicode。唉,為了這個filter支援Unicode,有些人在delphiBBS上苦苦守候了六七年。但delphi2007的UI(可視化界面)還不是全面支援Unicode。比方,你的DBGrid就不能支援中文、韓文(不說狗屁日文)、阿拉伯文等同一時候顯示。原有的Vcl架構大量採用string聲明而不是WideString,據李維講,CodeGear會将Vcl全面支援Unicode,但要考慮一種最佳的平衡方法。

6、非常棒的DBX4。dbExpress将要統一混亂的資料庫連接配接元件,底層全部改寫。無論在Win32還是Win64上,可相容本機代碼和托管代碼。呵呵,舉個樣例,DBX4可支援ADO,也能夠支援ADO.Net。這一點,M$也做不到。DBX4新增加了Connection

Pool進階元件。更令人心動的是,開放Driver

Source,可自行擴充屬性和方法,哈哈,ColreLab公司這回可高價賣不出去dbExpress驅動了。DBX4也全面支援Uicode。

7、Delphi2007 for

Win32支援Ajax技術—Intraweb9.0。Intraweb9.0元件封裝了Ajax,以事件方式來驅動程式。Intraweb9.0的Ajax技術支援斷點跟蹤調試,簡單到跟你調試其它delphi程式一樣。Intraweb9.0不愧是封裝javascript的上乘之作。事實上,話又說回來了,這一兩年流行的Ajax技術,事實上無非就是javascript封裝而已。而Intraweb已經在六七年将javascript技術做得爐火純青。更令人汗顔的是,很多所謂的web2.0新技術調試Ajax時候,仿佛又回到20多年前的C程式設計時代,不停地用Printf列印調試。Ajax技術也并不是是什麼進階技術,僅僅是web程式設計一種無奈的選擇。不久未來,應該是屬于智能client平台。

個人的幾點看法:

1、說實話這裡我經曆過大喜-&gt;大悲-&gt;大喜的三次變化。最初聽說Delphi重回Win32,當然是大喜,個人比較不爽.Net,由于給别人敲代碼還要别人裝無用的東西才幹運作(當然對象是企業的話,沒什麼所謂);之後在CSDN聽到一些殘缺不全的消息說D2007又要裝.Net了?轉而失望;幸而從這裡看,僅僅是裝.Net的開發時環境而已,運作庫并不須要,至此疑慮全消。并且聽說D2007還能針對2000、XP和Vista多種系統公布不同的程式?這個功能太棒了!曾經寫的程式,總是部分人能用部分人不能,想出多版本号的話,自己多裝個系統在那個系統下編譯吧,這不是一般的麻煩。希望此功能是真的。

2、啟動和編譯速度比D7還快,這個也非常吸引人。Delphi的編譯速度本來就非常有名了(用過C系列的就能對照出來)。之後聽說D2006又用一種新技術改進了記憶體管理,可惜D2006還沒裝,沒能體驗。這下D2007竟然比D7還快了,希望不是僅僅針對特殊項目弄出來的“示範效果”。

3、關于Unicode,強烈期待實作整個IDE環境的Unicode化,某次寫跟韓文有關的程式已經被整慘過了.... -_,-

這點Delphi須要向C#靠攏。

4、盡管IntraWeb自上次寫日志來,還暫時沒安排到學習時間。隻是偶還是相信那位Delphi達人的話,相信它的前途。如今都出到9.0了,我覺得假設Delphi在B/S上面能搶回份額,微軟将會面臨非常大的挑戰。

<b>Delphi 2007 </b><b>下安裝</b><b> Shell </b><b>元件</b><b></b>

内容摘要:本文介紹Delphi 2007 下安裝 Shell 元件

Delphi 2007 中沒有 Shell

元件,但在Delphi的lib檔案夾存在相關的源檔案,并且在C:WindowsSystem32檔案夾下存在vclshlctrls100.bpl的運作時包,隻有沒有設計時包。

但在Delphi的Demo中有一個ShellControls,安裝其檔案夾下的 dclshlctrls.dpk 就可以安裝成功Shell元件。

注1:Shell元件安裝後還是在元件面闆的Sample頁中;

注2:Delphi2007的Demo檔案夾在:我的電腦-共享文檔-RAD Studio-Demos-DelphiWin32-VCLWin32;

在 使用Shell這樣的元件時,程式編譯後都會出現以下的警告:[DCC Warning] Unit1.pas(7): W1005 Unit

‘ShellCtrls’ is specific to a platform

,假設您不想讓他顯示的話,能夠在你project相關源檔案的開頭加上以下代碼:

{$WARN UNIT_PLATFORM OFF}

<b>Delphi</b><b>歸來,重燃開發者的信心</b><b></b>

内容摘要:江元麟表示:「眼下僅僅有Microsoft Visual

C++支援64位,但我們累積了非常多Delphi的Library群組件,基于穩定度及開發時效的考慮,并不希望換開發環境,眼下做法是花非常多力氣和C++整合。……我今天就是來問Delphi什麼時候支援64Bit?」

CodeGear台灣區産品經理李維介紹完新産品,聽衆迫不及待的走向講台,紛紛問起新産品的相容問題,知網生物識别科技技術長江元麟也是當中一位,他特地來問一個問題,由于這将影響到公司未來産品的開發效率。

Windows Vista出現帶來64位新挑戰。知網生物識别科技去年面臨客戶要求在Vista的Content

Menu技術上支援64位檔案指紋加密。江元麟表示:「眼下僅僅有Microsoft Visual

從1995年發表1.0版後,12年來,Delphi曆經11個版本号更疊,從16位的1.0到.NET平台的BDS

2006。開發部門獨立成立CodeGear 後,又回到原生Win32環境下的Delphi 2007 for

Win32。江元麟24年程式開發經驗,一路見證了Delphi的變化。

從1987年開始接觸Borland,江元麟用過Turbo Pascal和Turbo

C。1995年,由于工作須要開始使用Delphi。2000年,他投入生物識别産業,繼續使用Delphi

5開發,他指出:「Delphi有一個非常好的長處是能夠開發自己的元件,它的元件讓我們的産品開發加速非常快。新進project師能立即就作一些簡單的開發,這是Delphi最優秀的地方。」

相較于當時其它開發工具,他覺得:「VB當時沒辦法全然用對象導向的方式去開發元件,比較不是給Engineer用,而是給Power

User使用。而C++要客制化元件難度頗高,它的平台沒有那麼靈活。」

為何一直用Delphi?江元麟解釋說:「是由于它的平台,非常多Source

Code都有釋出,是以你能夠開發一些真的是自己會用到的基層元件。我們公司的元件已經累積5年到10年都有,一個元件能夠撐那麼久,代表它非常穩定了,相對的我們公司的産品出來品質是非常好的,這也是Delphi的貢獻……這也是為什麼,我們甯可在Delphi上花力氣結合C++來處理新挑戰。」3年前,知網的識别軟體能讓Pentium

4 處理器在1秒内辨識十萬枚指紋,是當時國外最高速度的3倍,他說:「這當中有一部份是由Delphi編譯出來的程式代碼效率相當好的貢獻。」

盡管當天江元麟的問題沒有立即的解決方式,但對于脫離Borland後的GodeGear,他表示:「蠻喜歡分割出來的CodeGear,曾經步調非常慢,如今步調非常快,我比較喜歡,聽李維傳遞的訊息,感覺比較有活力,但希望能維持曾經的速度和品質。兩年前看到Borland公司非常亂,覺得非常遺憾,周圍的人兩年前已經慢慢轉到C#去了。」,他接着說:「我們本來去年要考慮轉成C#,如今要又一次考慮了

<b>編碼的藝術</b><b></b>

我在本文中要談的不是編碼的技術實作,我所關注的是關于編碼的風格的問題。我在編寫了e速的編碼規範之後,産生了要寫一些關于程式編碼風格的念頭;是以,就有了以下的文章,這些僅僅是本人的想法,可能在文章中還有一些未盡如人意的地方,是以肯請大家能夠給與諒解。

非常多人在談到編碼的藝術時,總會說我的程式怎麼怎麼的厲害,功能多麼的強大,好像什麼事情都能完畢一樣;可是去運作他的程式,bug不斷;連他自己都不知道錯在了什麼地方。打開他的程式一看,代碼寫的淩亂不堪;命名上不規範,為了偷懶和簡便有些命名幹脆就用一個字母或者其它的簡單符号取代,甚至于有些代碼連他自己也搞不清是幹什麼了,更不要說怎樣讓别人去改動了….本人編碼也快4個年頭了,像上述的樣例遇見過不少,整個程式改動起來實在是頭疼。

的确,一件好的藝術品不在于其功能是多麼的完好,而在于别人贊賞起來是否有它内在的美和是否非常easy就把它從雜貨堆裡一眼就能辨認出來;畢竟它是藝術品而非日用品。我們敲代碼也是同樣,假設程式中的格式非常随意,比如對數組做循環,一會兒採用下标變量從下到上的方式,一會兒又用從上到下的方式;對字元串一會兒用s

t r c p y做複制,一會兒又用f o r循環做複制;等等。這些變化就會使人非常難看清實際上究竟是怎麼回事了。

寫好一個程式,當然須要使它符合文法規則、修正當中的錯誤和使它運作得足夠快,可是實際應該做的遠比這多得多。程式不僅須要給計算機讀,也要給程式猿讀。一個寫得好的程式比那些寫得差的程式更easy讀、更easy改動。經過了怎樣寫好程式的訓練,生産的代碼更可能是正确的。

凝視:凝視是幫助程式讀者的一種手段。可是,假設在凝視中僅僅說明代碼本身已經講明的事情,或者與代碼沖突,或是以精心編排的形式幹擾讀者,那麼它們就是幫了倒忙。最好的凝視是簡潔地點明程式的突出特征,或是提供一種概觀,幫助别人了解程式。在标注凝視的同一時候,應該注意以下的問題:

不要大談明顯的東西。凝視不要去說明明确白的事,比方i +

+能夠将i值加1等等。凝視應該提供那些不能一下子從代碼中看到的東西,或者把那些散布在很多代碼裡的資訊收集到一起。當某些難以捉摸的事情出現時,凝視能夠幫助澄清情況。假設操作本身非常明了,反複談論它們就是畫蛇添足了;給函數和全局資料加凝視。凝視當然能夠有價值。對于函數、全局變量、常數定義、結構和類的域等,以及不論什麼其它加上簡短說明就能夠幫助了解的内容,我們都應該為之提供凝視。全局變量常被分散使用在整個程式中的各個地方,寫一個凝視能夠幫人記住它的意義,也能夠作為參考。放在每個函數前面的凝視能夠成為幫人讀懂程式的台階。假設函數代碼不太長,在這裡寫一行凝視就足夠了。有些代碼原本非常複雜,可能是由于算法本身非常複雜,或者是由于資料結構非常複雜。在這些情況下,用一段凝視指明有關文獻對讀者也非常有幫助。此外,說明做出某種決定的理由也非常有價值。

職業程式猿也常被要求凝視他們的全部代碼。可是,應該看到,盲目遵守這些規則的結果卻可能是丢掉了凝視的真谛。凝視是一種工具,它的作用就是幫助讀者了解程式中的某些部分,而這些部分的意義不easy通過代碼本身直接看到。我們應該盡可能地把代碼寫得easy了解。在這方面你做得越好,須要寫的凝視就越少。好的代碼須要的凝視遠遠少于差的代碼。

編碼的風格:全局變量應該採用具有描寫叙述意義的名字,局部變量用短名字。函數採用動作性的名字。給神奇的數起個名字。現實中存在很多命名約定或者本地習慣。常見的比方:指針採用以p結尾的變量名,比如n

o d e p;全局變量用大寫開頭的變量名,比如G l o b a l;常量用全然由大寫字母拼寫的變量名,如C O N S T A N T

S等。命名約定能使自己的代碼更easy了解,對别人寫的代碼也是一樣。這些約定也使人在寫代碼時更easy決定事物的命名。對于長的程式,選擇那些好的、具有說明性的、系統化的名字就更加重要。

保持一緻性。要準确。以縮行形式顯示程式結構。使用表達式的自然形式。利用括号排除歧義。分解複雜的表達式。要清晰。當心副作用。使用一緻的縮行和加括号風格。為了一緻性,使用習慣使用方法。用else-if

處理多路選擇。避免使用函數宏。給宏的體和參數都加上括号。這些都是非常瑣碎的事情,但卻又是非常有價值的,就像保持書桌整潔能使你easy找到東西一樣。與你的書桌不同的是,你的程式代碼非常可能還會被别人使用。

用縮行顯示程式的結構。採用一種一緻的縮行風格,是使程式呈現出結構清晰的最省力的方法。

用加括号的方式排除二義性。括号表示分組,即使有時并不必要,加了括号也可能把意圖表示得更清晰。在混合使用互相無關的運算符時,多寫幾個括号是個好主意。C語言以及與之相關的語言存在非常險惡的優先級問題,在這裡非常easy犯錯誤。比如,由于邏輯運算符的限制力比指派運算符強,在大部分混合使用它們的表達式中,括号都是必需的。

利用語言去計算對象的大小。不要大談明顯的東西。給函數和全局資料加凝視。不要凝視不好的代碼,應該重寫。不要與代碼沖突。澄清情況,不要添亂。

界面的風格:隐蔽實作的細節。不要在使用者背後搞小動作。在各處都用同樣方式做同樣的事。釋放資源與配置設定資源應該在同一層次進行。在低層檢查錯誤,在高層處理。僅僅把異經常使用在異常的情況。

寫良好的代碼更easy閱讀和了解,差點兒能夠保證當中的錯誤更少。進一步說,它們通常比那些馬馬虎虎地堆起來的、沒有細緻推敲過的代碼更短小。在這個拼命要把代碼送出門、去趕上最後期限的時代,人們非常easy把風格丢在一旁,讓将來去管它們吧。可是,這非常可能是一個代價非常昂貴的決定。上面的一些陳述性的言語充分的說明了,假設對好風格問題重視不夠,程式中哪些方面可能出毛病。草率的代碼是非常壞的代碼,它不僅難看、難讀,并且經常崩潰。好風格應該成為一種習慣。假設你在開始寫代碼時就關心風格問題,假設你花時間去審視和改進它,你将會逐漸養成一種好的程式設計習慣。一旦這樣的習慣變成自己主動的東西,你的潛意識就會幫你照料很多細節問題,甚至你在工作壓力下寫出的代碼也會更好

<b>Delphi</b><b>面向對象的程式設計方法</b><b></b>

Delphi的程式設計語言是以Pascal為基礎的。Pascal語言具有可讀性好、編寫easy的特點,這使得它非常适合作為基礎的開發語言。同一時候,使用編譯器建立的應用程式僅僅生成單個可運作檔案(.EXE),正是這樣的結合,使得Pascal成為Delphi這樣的先進開發環境的程式設計語言。

本章中,我們将讨論Object

Pascal的主要特點,并解說怎樣在事件處理過程和其它應用程式中,使用它來編制程式代碼。本章将解說Delphi應用程式中最經常使用的Object

Pascal文法,而不是Pascal語言的一切細節。假設您全然不熟悉Pascal程式設計,請參閱一些基礎的Pascal教程。假設您具有程式設計經驗,并能熟練地使用其它流行程式語言,您将在本章的Object

Pascal中發現一些同樣的概念。假設您已經熟悉了Borland Pascal,就能夠高速浏覽或跳過本章。

2.1 編寫Object Pascal程式代碼

在前邊的章節中,我們通過例程,已經編寫了幾行簡單的代碼。在本章中,我們将從熟悉Pascal程式設計的角度,配合執行個體,解說Object

Pascal程式設計的基本方法。

在編寫自己的Object

Pascal程式時,要注意程式的可讀性。Pascal語言是英式結構語言,在程式中選擇合适的縮排、大寫和小寫風格,并在須要時将程式代碼分行,會使得程式代碼能夠非常easy地被自己和他人讀懂。一般的程式猿都有這樣的體驗:假設不給程式加上适當的注解,一段時間後,自己也難以理清程式的流程。給程式及時地加上凝視是良好的程式設計習慣。Delphi的凝視須要加注在{}之間,編輯器會把它們處理成為空白。Delphi保留了Borland

Pascal編輯器的風格,keyword採用黑體字,被凝視的部分會變暗,這使得程式設計風格良好,易讀易寫。

2.1.1 編寫指派語句

在事件處理過程中,最經常使用到的工作就是把一個新值賦給一個屬性或變量。在設計使用者界面時,能夠使用Object Inspector(Object

Inspector)來改變其屬性;但有時須要在程式運作時改變屬性的值,并且有些屬性僅僅能在運作時改變,這些屬性在Delphi的線上幫助的“Proprety”主題中被标為運作期屬性。進行這樣的改變,就必須使用指派語句。

下文的指派語句表征一個OnClick事件。當button按動後,将編輯框部件Edit1的Color屬性置為clRed:

procedure TForm1.Button1Click(Sender: TObject);

Edit1.Color := clRed;

當按動button後指派語句被運作,編輯框變成紅色。

在語句中,部件的名稱在屬性前,中間用“.”表示屬性的所屬關系。這樣就準确地指定了要将clRed值賦給哪一部件的哪一屬性。指派号為“:=”,不論給屬性還是給變量指派,都是将右邊的值賦給左邊的屬性或變量。

當将一個屬性值、變量、常量或文本資料賦給屬性或變量時,所指派的類型和接受此值的屬性或變量的類型應同樣或相容。一個屬性或變量的類型定義了此屬性或變量的可能值集合,也定義了程式代碼能夠運作的運算。在前邊的例程中,編輯框部件的Color屬性和clRed的類型都是TColor。能夠在線上幫助中找到一個屬性的類型;第二種方法是在Object

Inspector中標明該屬性值段,并按下F1鍵,則類型将在屬性說明的結尾處列出,比如Color屬性列出下邊的語句:

Property Color : TColor;

有些屬性是僅僅讀(Read Only)的,它們僅僅能被讀取,不能被改變。請查閱線上幫助,在Delphi中這些僅僅讀屬性都有注解。

2.1.2 辨別符的說明與使用

辨別符是Delphi應用程式中一些量的名稱,這些量包括變量(var)、常量(const)、類型(type)、過程(procedure)、方法(Method)及其它,Object

Pascal 在應用辨別符時,必須首先說明它們。Object

Pascal是強類型語言,它的編譯器能夠檢查確定賦給變量或屬性的值是正确的類型,以便于您改正錯誤。由于Object

Pascal是編譯語言,是以Delphi的運作速度要比使用解釋語言快得多。在使用辨別符前說明它們,能夠降低程式錯誤并增加代碼的效率。

2.1.2.1 變量

變量是程式代碼中代表一個記憶體位址的辨別符,而此位址的記憶體内容在程式代碼運作時能夠被改變。在使用變量前必須對它進行說明,即對它進行命名,并說明它的類型。在全部變量說明曾經加上保留字var。變量說明左邊是變量的名稱,右邊則是該變量的類型,中間用(:)隔開。

var

Value ,Sum : Integer;

Line : String;

在視窗中增加一個名稱為Edit1的編輯框,再增加一個名稱(屬性Name)為Add的button部件,并建立例如以下的事件處理過程:

procedure TForm1.addClick(Sender: TObject);

X , Y: Integer;

X := 100;

Y := 20;

Edit1.Text := IntToStr(X +

Y);

在本例中,當按動ADDbutton時,編輯框中顯示值120。在Object

Pascal中,必須確定變量或屬性被賦予類型同樣或相容的值。您能夠嘗試将賦給X的值改為100.0,或去掉IntToStr函數,在編譯時會出現類型不比對的錯誤,這也說明了Object

Pascal強類型語言的特點。

2.1.2.2 提前定義類型

Object Pascal有多個提前定義的資料類型,您能夠說明不論什麼這些類型的變量:

整形:Integer的範圍是-32768到32767,占2位元組的記憶體;Shortint從-128到127,占1位元組記憶體;Longint從-2147443648到2147483647

占4位元組記憶體;Byte從0到255,占1位元組;Word從0到65535,占2位元組記憶體。它們都是沒有小數部分的數字。

實型:Single能夠包括7到8位有效小數部分,占用4位元組的記憶體;Double類能夠包括15到16位有效小數部分,占用8位元組的記憶體;Extended類型包括19到20位有效小數部分,占用10位元組記憶體;Comp能夠包括19到20位有效小數部分,占用8位元組記憶體。以上實數類型僅僅有在8087/80287選項[N+]打開才幹夠使用。Real能夠包括11到12位有效小數部分,占用6位元組記憶體。它僅僅有在和曾經Borland

Pascal相容的情況下才使用,否則應使用Double或Extended。

布爾型:Boolean,僅僅包括true或False兩個值,占用1位元組記憶體。

字元型:Char,一個ASCII字元;字元串類型String一串最長可達255個ASCII字元。

指針型:Pointer,能夠指向不論什麼特定類型。

字元串型:PChar,是一個指向以零結尾的字元串的指針。

除了提前定義類型外,Delphi還有自行定義的類型。上述例程的TColor就是這樣的類型。此外,使用者還能夠定義自己的資料類型,這部分内容将在下文中具體講述。

整型類别和實型類别都各有五種類型,同一類别中,全部的類型與其它同類别的都相容,您能夠将一種類型的值賦給同樣類别中不同類型的變量或屬性,而僅僅須要這個值的範圍在被指派的變量或屬性的可能值範圍内。比如,對于一個Shortint型的變量,能夠接受在-128到127範圍内的随意整數,比如Shortint類型的7;您不能将300賦給它,由于300已經超出了Shortint的範圍了。将範圍檢查功能打開(選用Options|Project,并在Compiler

Options Page中選擇Range Checking),将會檢查出一個範圍錯誤;假設Range

Checking沒有被打開,那麼程式代碼将能夠運作,但被指派的值将不是您期望的值。

在一些情況下,您能夠進行不同類型的變量或屬性的指派。一般來說,能夠将一個較小範圍的值賦給一個較大範圍的值。比如,您能夠将整型值10賦給一個接受實型值的Double屬性而使得值成為10.0,但假設将一個Double類型的值賦給整形變量,則會出現類型錯誤。假設您不清晰類型的相容性,能夠參閱Delphi的線上幫助中“Type

Compatibility and Assignment Compatibility”主題。

2.1.2.3 常量

常量在說明時就被賦予了一個值,在程式運作過程中是不可改變的。以下的樣例說明了三個常量:

const

Pi = 3.14159;

Answer = 342;

ProductName =

"Delphi";

象變量一樣,常量也有類型。不同的是,常量假設其類型就是常量說明中其所代表的值的類型。上文的三個常量的類型各自是real型、整形、字元串型。常量用“= "

表示兩邊的值是相等的。

2.1.3 過程與函數

過程與函數是程式中運作特定工作的子產品化部分。Delphi的運作庫包括很多過程與函數以供您的應用程式調用。您不必了解過程與函數的邏輯,但要知道過程與函數的用途。在對象中說明的過程和函數稱為方法(Method)。全部的事件處理過程都是過程,以保留字procedure開頭。每個事件處理過程僅僅包括了當這一事件發生時須要運作的程式代碼。在事件處理過程中使用Delphi已經存在的過程與函數,僅僅需在程式代碼中調用它們就可以。

2.1.3.1 一個調用Delphi方法的簡單例程

下文将通過對一個Memo部件的文本進行剪切、拷貝、粘貼、清除等編輯的應用程式編制,介紹使用Delphi過程和函數的調用方法。

Memo(備注)部件有一個CutToClipboard方法,實作将使用者在memo中選擇的文本移到剪貼闆上去。由于這個功能已經被建立在此方法中了,是以您僅僅需知道這種方法做什麼以及怎樣使用它就可以。

以下的語句表明怎樣調用一個名為Memo1的memo部件的CutToClipboard方法:

Memo1.CutToClipboard;

通過指定Memo1的名稱,說明調用哪一個部件的CutToClipboard方法。假設不指明對象名稱,Delphi會顯示Unknown

identifier錯誤。當該事件處理過程被觸發,程式會運作CutToclipboard中的語句,将Memo1中的文本剪貼到剪貼闆上去。

下文的例程展示了怎樣調用Delphi的方法,實作将備注部件的文本資訊剪切、複制到剪貼闆上;将剪貼闆上的标記文本粘貼到備注中,清除備注部件中的全部文本等四個功能。

打開一個新的空視窗,增加一個memo部件和四個button,并排列整齊。改變button部件的Name屬性,分别命名為Cut,Copy,Paste,Clear。您會發現,當Name屬性發生改變時,Caption屬性将發生對應的變化。在Caption屬性前加标“&amp;”号設立加速鍵

将memo部件的ScrollBars屬性設為ScVertical,以便加上滾行條。将WordWrap屬性設定為True,這樣當使用者輸入文本到達Memo部件的右邊緣時會自己主動回行。将Line屬性第一行的Memo1文本删除,使得memo部件在初始顯示時為空的。

為每個button建立例如以下的事件處理過程:

procedure TForm1.CutClick(Sender: TObject);

procedure TForm1.CopyClick(Sender:

TObject);

Memo1.CopyToClipboard;

procedure TForm1.PasteClick(Sender: TObject);

Memo1.PasteFromClipboard;

TForm1.ClearClick(Sender: TObject);

Memo1.clear;

運作此程式。您能夠在備注部件中輸入文本,在進行了文本的标記後,能夠随意地進行剪切、拷貝、粘貼和清除。當button被按動時,就調用對應的過程進行處理。使用者能夠通過查閱線上幫助進行Memo部件的Topic

Search,在Memo Component項中查閱Method,會得到以上過程的具體說明。

2.1.3.2 調用Delphi的含參過程

有些過程要求使用者指明參數。被調用的過程會在運作時使用傳入的參數值,這些值在過程中被覺得是已經被說明的變量。比如,LoadFromFile方法在TString對象中被說明為:

Procedure LoadFromFile(const FileName: String);

在調用這一過程時,應指明FileName參數是要裝入的檔案名。以下的程式将先打開Open對話框,當您選擇了一個檔案後,Delphi将把該檔案讀入一個Memo部件:

OpenDialog.Execute;

Memo1.lines.LoadFromFile(OpenDialog.FileName);

2.1.3.3 使用Delphi函數

與過程一樣,函數的程式代碼也運作特定的工作。它和過程的差别為:函數運作時會傳回一個值,而過程則沒有傳回值。函數能夠用來賦給一個屬性或變量;也能夠使用傳回值來決定程式的流程。

前文中我們實際上已經接觸過了函數。在講述變量時,曾用到過以下的程式段: Edit1.Text := IntToStr(X +

Y);當中,IntToStr(Value)把一個LongInt類型的數值轉化為字元串的值,Value是IntToStr唯一的參數,它能夠是一個整形的值、變量、屬性或産生整形值的表達式。調用函數,必須把傳回值賦給和此傳回值類型相容的變量或屬性。

有些函數傳回一個True或False的布爾量,使用者的程式能夠依據傳回值來決定跳轉。下文的例程講述了函數傳回值為Boolean的推斷使用方法:

在視窗中增加一個ColorDialog對象和一個Name屬性為ChangeColor的button。為button的OnClick事件建立事件處理步驟例如以下:

procedure TForm1.ChangeColorClick(Sender: TObject);

if

ColorDialog1.Execute then

Form1.Color := ColorDialog1.Color

else

Form1.Color := clRed;

此事件處理過程使用一個傳回Boolean值的Execute方法。按動button,并在顔色對話框中選擇一個顔色。假設按動OKbutton,ColorDialog.Execute方法将傳回True,則Form1.Color将被指派為ColorDialog1.Color,視窗顯現您選用的顔色;假設按動顔色對話框的Cancelbutton,方法将傳回False值,視窗将變為紅色。

2.1.4 跳轉語句

Object Pascal的跳轉語句有if和case兩個。

2.1.4.1 if語句

if語句會計算一個表達式,并依據計算結果決定程式流程。在上文的例程中,依據ColorDialog.Execute的傳回值,決定視窗的背景顔色。if保留字後尾随一個生成Boolean值True或False的表達式。一般用“=”作為關系運算符,比較産生一個布爾型值。當表達式為True時,運作then後的語句。否則運作else後的代碼,if語句也能夠不含else部分,表達式為False時自己主動跳到下一行程式。

if語句能夠嵌套,當使用複合語句表達時,複合語句前後需加上begin…end。else保留字前不能加“;”,并且,編譯器會将else語句視為屬于最靠近的if語句。必要時,須使用begin…end保留字來強迫else部分屬于某一級的if語句。

2.1.4.2 case語句

case語句适用于被推斷的變量或屬性是整形、字元型、枚舉型或子界型時(LongInt除外)。用case語句進行邏輯跳轉比編寫複雜的if語句easy閱讀,并且程式代碼整形較快。

以下的例程顯示一個使用case語句的視窗:

建立例如以下的事件處理過程:

Number :

Number := StrToInt(Edit1.Text);

case Number

of

1,3,5,7,9: Label2.Caption := ‘奇數‘;

0,2,4,6,8: Label2.Caption :=

‘偶數‘;

10..100:

Label2.Caption := ‘在10到100之間‘;

Form1.Color := clBlue;

Label2.Caption :=

‘大于100或為負數‘;

運作程式,當Edit1部件接受到一個值,并按動“OK”button觸發程式後,Number便被指派為使用者輸入的數值。case語句依據Number的值推斷該運作哪一條語句。象if語句一樣。case語句也有可選擇的else部分。case語句以end結尾。

2.1.5 循環語句

Object Pascal的循環語句有三種:repeat、while和for語句。

2.1.5.1 repeat語句

repeat語句會反複運作一行或一段語句直到某一狀态為真。語句以repeat開始,以until結束,其後尾随被推斷的布爾表達式。參閱以下的例程:

i := 0;

repeat

i := i+1;

Writen(i);

until

i=10;

當此語句被運作時,視窗的下方會出現1到10的數字。布爾表達式 i=10

(注意,與其它語言不同的是,“=”是關系運算符,而不能進行指派操作)直到repeat..until程式段的結尾才會被計算,這意味着repeat語句至少會被運作一次。

2.1.5.2 while語句

while語句和repeat語句的不同之處是,它的布爾表達式在循環的開頭進行推斷。while保留字後面必須跟一個布爾表達式。假設該表達式的結果為真,循環被運作,否則會退出循環,運作while語句後面的程式。

以下的例程達到和上面的repeat例程達到同樣的效果:

while i&lt;10 do

writeln(i);

2.1.5.3 for語句

for語句的程式代碼會運作一定的次數。它須要一個循環變量來控制循環次數。您須要說明一個變量,它的類型能夠是整形、布爾型、字元型、枚舉型或子界型。

以下的程式段會顯示1到5的數字,i為控制變量:

i : integer;

for i := 1 to 5 do

以上介紹了三種循環語句。假設您知道循環要運作多少次的話,能夠使用for語句。for循環運作速度快,效率比較高。假設您不知道循環要運作多少次,但至少會運作一次的話,選用repeat..until語句比較合适;當您覺得程式可能一次都不運作的話,最好選用while..do語句。

2.1.6 程式子產品

程式子產品在Object

Pascal中是非常重要的概念。它們提供了應用程式的結構,決定了變量、屬性值的範圍及程式運作的過程。它由兩個部分組成:可選擇的說明部分和語句部分。假設有說明部分,則必在語句部分之前。說明部分包括變量說明、常量說明、類型說明、标号說明、程式,函數,方法的說明等。語句部分叙述了可運作的邏輯行動。

在Delphi中,最常見的程式子產品便是事件處理過程中的程式子產品。以下的事件處理過程是含有變量說明部分的程式子產品:

procedure TForm.Button1Click(Sender Tobject);

var {程式子產品的說明部分}

Name : string;

begin {程式子產品的語句部分}

Name := Edit1.Text;

Edit2.Text := ‘Welcome to Delphi‘+Name;

end; {程式子產品結束}

庫單元也是程式子產品。庫單元的interface部分含有庫函數、類型、私有,公有域的說明,也能夠含有常量、變量的說明。這一部分能夠作為程式子產品的說明部分。在庫單元的implementation部分中通常含有各種事件處理過程,它們能夠視為子產品的語句部分,是事件處理子產品。庫單元子產品結束于庫單元結束的end.處。

程式子產品中能夠包括其它的程式子產品。上文庫單元子產品中含有事件處理子產品。而庫單元子產品實際是在project程式子產品中。

全部的Delphi應用程式都有同樣的基本結構。當程式逐漸複雜時,在程式中增加子產品就可以。比如在庫單元子產品中增加事件處理子產品,向project中增加庫單元子產品等。子產品化程式設計使得程式結構良好,并且對資料具有保護作用。

2.1.7 關于作用範圍

2.1.7.1 辨別符的作用範圍

一個變量、常量、方法、類型或其它辨別符的範圍定義了這個辨別符的活動區域。對于說明這個辨別符的最小程式子產品而言,此辨別符是局部的。當您的應用程式在說明一個辨別符的程式子產品外運作時,該辨別符就不在此範圍内。這意味着此時運作的程式無法訪問這個辨別符,僅僅有當程式再度進入說明這個辨別符的程式子產品時,才幹夠訪問它。

以下的示意圖表示一個含有兩個庫單元的project,每個庫單元中又各有三個過程或事件處理過程。

2.1.7.2 訪問其它程式子產品中的說明

您能夠在目前的程式子產品中訪問其它程式子產品中的說明。比如您在庫單元中編寫一個事件處理過程來計算利率,則其它的庫單元能夠訪問這個事件處理過程。要訪問不在目前庫單元中的說明,應在這個說明之前加上其它應用程式的名稱和一個點号(.)。比如,在庫單元Unit1中有事件處理過程CalculateInterest過程,如今您想在庫單元Unit2中調用這一過程,則能夠在Unit2的uses子句中增加Unit1,并使用以下的說明:

Unit1.CalculateInterest(PrincipalInterestRate : Double);

應用程式的代碼不能在一個子產品外訪問它說明的變量。事實上,當程式運作跳出一個子產品後,這些變量就不存在于記憶體中了。這一點對于不論什麼辨別符都是一樣的,無論事件處理過程、過程、函數還是方法,都具有這一性質。這樣的辨別符稱為局部變量。

2.1.7.3 依照作用範圍說明辨別符

您能夠在應用程式的不同地方說明一個辨別符,而僅僅需保證它們的有效範圍不同就可以。編譯器會自己主動訪問最靠近目前範圍的辨別符。

庫單元的全局變量一般能夠說明在保留字implementation後面。比如,以下的例程實作将兩個編輯框中的整數相加,顯示在第三個編輯框中。用到了一個整形的全局變量Count:

…implememntation

Count : Integer;

TForm1.AddClick(Sender:TObject);

FirstNumber,SecondNumber:Integer;

Count := Count + 1;

Counter.Text := IntToStr(Count);

FirstNumber := StrToInt(Edit1.Text);

SecondNumber := StrToInt(Edit2.Text);

Edit3.Text :=

IntToStr(FirstNumber+SecondNumber);

為了實作每按動一次buttonCount增加一次,必須對全程變量Count進行初始化處理。在程式庫單元的結尾處,最後一個end.保留字之前,增加保留字initialization和初始化Count的代碼:

initialization

Count := 0;

這樣當事件處理過程AddClick被觸發時,Count就會被增加一次,以表征計算次數。假設用面向對象程式設計,則Count能夠說明成視窗的一個域,這在下一節中将有講述。

2.1.8 編寫一個過程或函數

在您開發Delphi應用程式時,所需的大部分代碼都編寫在事件處理過程中,但有時仍然須要編寫不是事件處理過程的函數或過程。比如,您能夠把在多個事件處理過程中用得到語句編寫成過程,然後不論什麼事件處理過程、過程、函數都能夠象調用已經存在的過程或函數一樣直接調用它。長處是您僅僅需編寫一次代碼,并且程式代碼會比較清晰。

2.1.8.1 一個自行編寫的函數例程

在上文兩個數相加的程式中,假設編輯框中無值,則會使得程式出錯中斷。為避免這樣的情況,編寫以下的函數,檢查編輯框中是否有值,如無值,則提醒使用者輸入:

function NoValue(AnEditBox:TEdit):Boolean;

AnEditBox.Text=‘‘ then

AnEditBox.Color := clRed;

AnEditBox.Text := ‘請輸入整數值‘;

Result := True;

end

AnEditBox.Color := clWindow;

Result := False;

NoValue函數會檢查編輯框是否為空,假設是,編輯框顔色變紅,并提醒使用者輸入一個整數,然後函數傳回真值;Result保留字在Delphi中用來專指函數傳回值。在上文的例程中增加NoValue函數:

procedure TForm1.AddClick(Sender: TObject);

FirstNumber,SecondNumber : Integer;

if NoValue(Edit1)or

NoValue(Edit2) then

exit;

Counter.Text

:= IntToStr(Count);

假設當中的不論什麼一個傳回真值,則表示有編輯框空,會運作exit過程,使得目前的程式子產品停止運作,并使得編輯框出現輸值提示。當新值被輸入後,再運作程式時,紅色提示被隐去,恢複正常的計算狀态。

2.1.8.2 過程和函數的标題

每個過程或函數都以标題開始,當中包括過程或函數的名稱和它使用的參數。過程以保留字procedure開始,函數以保留字function開始。參數位于括号中面,每個參數以分号分隔。比如:

procedure validateDate(Day : Integer; month : Integer; Year : Integer);

您也能夠将同樣類型的參數組合在一起,則上述過程頭寫作:

procedure ValidateDate(Day, Month, Year : Integer);

函數在标題中還多了一項:傳回值的類型。以下是一個傳回值為Double型的函數标題:

function CalculateInterest(principal,InterestRate:Double):Double;

2.1.8.3 函數和過程中的類型說明

一個過程或函數程式子產品也含有說明部分和語句部分。說明部分能夠包括類型說明、變量說明、常量說明等。除了Object

Pascal語言中已經定義的類型之外,Delphi的應用程式還能夠建立新的資料類型。類型說明部分有保留字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) ;

在類型辨別符後面,用“=”号定義了新的類型。類型界定了變量的取值範圍,比如,TCount類型的變量必須是整形值;一個TPrimaryColor類型的變量僅僅能是red、yellow或blue等等。每個類型的名稱都是由字母T開始,這并不是必須的,但它是Delphi的慣例,在差别類型名和辨別符時非常實用。類型說明能夠是局部的,也能夠是全局的。假設您把它放在implementation後面,則表明對于庫單元來講,它是全局的,全部的事件處理過程和其它的過程、函數都能夠調用它。假設類型是在過程中被說明的,則是局部的,離開這一過程,該類型将失效。

一般來講,在過程和函數中,不論什麼類型說明都在變量說明之前,而不論什麼變量說明都在常量之前。可是,僅僅要遵從說明必須在過程與函數的标題之後,并且在程式代碼之前,即是有效的。

2.1.8.4 過程和函數的語句部分

過程或函數的語句部分由begin開始,end結束。函數須要一個傳回值。能夠将傳回值賦給函數名稱,也能夠将傳回值賦給Result變量。以下的例程将傳回值賦給函數名稱:

function CalculateInterest(Principal,InterestRate: Double):Double;

CalculateInterest := Principal * InterestRate;

将傳回值賦給Result變量也是能夠的,則上面的程式改為:

Result := Principal*InterestRate;

以下是這個函數的調用方法:

InterestEarned :=CalculateInterest(2000,0.012);

在Implementation後面的過程和函數,能夠且僅僅能被此庫單元的事件處理過程使用。要讓過程和函數能夠被其它的程式庫單元使用,則須要将過程或函數的标題部分放在庫單元中的interface部分,而把含标題的整個過程或函數放在庫單元的inplementation部分,并在要訪問這個過程或函數的庫單元的uses子句中增加說明這個過程或函數的庫單元名稱。

2.1.8.5 函數的遞歸調用

在Object

Pascal中,過程或函數必須先說明再調用。上文的NoValue函數必須在使用它的事件處理過程之前說明和運作,否則程式會報告一個未知辨別符的錯誤。

以上規則在遞歸調用時是例外情況。所謂遞歸調用,是指函數A調用函數B,而函數B又調用函數A的情況。在遞歸調用中,函數要進行前置,即在函數或過程的标題部分最後加上保留字forword。下文的例程是一個遞歸調用的典型樣例:

implementation

alpha:Integer;

Test2(var A:Integer):forword;

{Test2被說明為前置過程}

procedure Test1(var

A:Integer);

A :=A-1;

if A&gt;0 then

test2(A);

{經前置說明,調用未運作的過程Test2}

writeln(A);

procedure Test2(var

A:Integer);{經前置說明的Test2的運作部分}

A :=A div 2;

if A&gt;0

rhen

test1(A); {在Test2中調用已運作的過程Test1}

TForm1.Button1Click(Sender:TObject);

Alpha := 15;

{給Alpha賦初值}

Test1(Alpha); { 第一次調用Test1,遞歸開始}

button的OnClick事件處理過程給Alpha賦初值,并實作先減1再除2的循環遞歸調用,直到Alpha小于0為止。

2.1.8.6 過程和函數的參數

當您的程式代碼在調用一個過程或函數時,通經常使用參數傳遞資料到被調用的過程或函數中。最經常使用的參數有數值參數、變量參數和常量參數三種。

由被調用過程或函數定義的參數為形參,而由調用過程或函數指明的參數叫實參。在NoValue函數中,說明函數體中的AnEditBox是形參,而調用時在if

NoValue(Edit1)…中,Edit1是實參。

數值參數在運作過程中僅僅改變其形參的值,不改變事實上參的值,即參數的值不能傳遞到過程的外面。試看以下的例程:

procedure Calculate(CalNo:Integer);

CalNo := CalNo*10;

用以下例程調用Calculate函數:

Calculate(Number);

Edit2.Text := IntToStr(Number);

Number接受由編輯框1輸入的數值,經Calculate過程運算。它是一個數值型實參。在進入Calculate函數後,會把Number實參拷貝給形參CalNo,在過程中CalNo增大十倍,但并未傳遞出來,是以Number值并未改變,在編輯框2中顯示仍然是編輯框1中的輸入值。形參和實參占用不同的記憶體位址,在過程或函數被調用時,将實參的值複制到形參占用的記憶體中。是以出了過程或函數後,形參和實參的數值是不同的,但實參的值并不發生變化。

假設您想改變傳入的參數值,就須要使用變量參數,即在被調用程式的參數表中的形參前加上保留字var。比如:

procedure Calculate(var CalNo : Integer);

則CalNo并不在記憶體中占領一個位置,而是指向實參Number。當一個變參被傳遞時,不論什麼對形參所作的改變會反映到實參中。這是由于兩個參數指向同一個位址。将上一個例程中過程頭的形參CalNo前面加上var,再以同樣的程式調用它,則在第二個編輯框中會顯示計算的結果,把第一個編輯框中的數值放大十倍。這時形參CalNo和實參Number的值都是Nnmber初始值的10倍。

假設當過程或函數運作是要求不改變形參的值,最保險的辦法是使用常量參數。在參數表的參數名稱前加上保留字const能夠使一個形參成為常量參數。使用常量參數取代數值參數能夠保護您的參數,使您在不想改變參數值時不會意外地将新的值賦給這個參數。

2.1.9 定義新的資料類型

Object

Pascal有一些系統提前定義的資料類型,在2.1.2中已經對它們作了介紹。您能夠利用這些資料類型以建立新的資料類型來滿足程式的特定須要。以下簡單地叙述了您能建立的主要資料類型,如枚舉型、子界型、數組型、集合型、記錄型、對象型等。

2.1.9.1 枚舉類型

一個枚舉型的說明列出了全部這樣的類型能夠包括的值:

Tdays=( Sunday

,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday);

能夠定義上述枚舉類型的變量:

DayOfWeek:TDays;

在枚舉型中,括号中的每個值都有一個由說明它的位置決定的整形值。比如Sunday有整形值0,Monday有整形值1等。您能夠把DayOfWeek說明為一個整形變量,并将一星期的每一天賦一個整形值以達到同樣的效果,但用枚舉型會使得程式可讀性好,編寫easy。當您在枚舉型中列出值時,您同一時候說明了這個值是一個辨別符。比如您的程式中假設已經含有TDays類型且說明了DayOfWeeks變量,則程式中便不能使用Monday變量,由于它已經被說明為辨別符了。

2.1.9.2 子界類型

子界型是下列這些類型中某範圍内的值:整形、布爾量、字元型或枚舉型。在您想限制一個變量的取值範圍時,子界型是非常實用的。

Thours = 0..23;

TValidLetter = ‘A‘ .. ‘F‘;

TDays =

( Sunday ,Monday,Tuesday,Wednesday,Thursday,

Friday,Saturday); {枚舉型}

TWorkDay = Monday..Friday; {一個TDays型的子界}

子界型限定了變量的可能取值範圍。當範圍檢查打開時,(在庫單元的Implementation後面有{$R*.DFM}字樣表示範圍檢查打開,否則您能夠在Options|Project|Complier

Options中選擇Range Cheking來打開範圍檢查),假設變量取到子界以外的值,會出現一個範圍檢查錯誤。

2.1.9.3 數組類型

數組是某種資料類型的有序組合,當中每個元素的值由其相對位置來指定,您能夠在數組的某個位置上放置資料,并在須要時使用這些資料。以下的類型說明了一個Double型的數組變量:

Check : array [1..10] of Double;

它表示Check指向一個含有10個Double型元素的資料串列,代表每個元素的是1到10之間的數字,稱為索引。數組的每一項由數組名稱加上[]中的索引來表示。Check包括10個變量,Check[1]表示第一個變量。您也能夠把數組定義成類型:

TCheck = array[1..10] of Double;

則變量說明改為:

Check :TCheck;

您能夠通過給數組指派等方法來使用數組。以下的語句将0.0賦給Check數組中的全部元素:

for J := 1 to 10 do

Check[J] := 0.0;

數組也能夠是多元的,以下的類型定義了一個20行、20列的數組。

Ttable = array[1..20,1..20] of Double;

table1:TTable;

想将這一表格的全部資料初始化為0.0,您能夠使用for循環:

Col,Row:Integer;

for Col :=1 to 20 do

for

Row := 1 to 20 do

Table1[Col,Row] := 0.0;

2.1.9.4 字元串類型

字元串類型事實上是一個一維的字元數組。當您說明一個字元串型的變量時,您應當指明這個字元串的大小,以下是說明字元串類型的樣例:

MyString: string[15];

MyName: MyString;

則變量MyName被說明成為最多能夠包括15個字元。假設您沒有說明字元串的大小,Delphi會覺得字元串包括最大值255個字元。給字元串指派能夠直接使用單引號括起的字串指派:

MyName := ‘Frank.Smith‘;

或MyName := ‘張明‘;

由于MyName是一個能夠包括15個字元的MyString型變量,上文的兩個的變量都是有效的,一個漢字能夠視作兩個字元。當您給字元串型變量賦的值多于定義數值時,比如将MyName賦為‘FrankSmith.Franklin’,則Delphi僅僅會接受前15個字元‘FrankSmith.Fran’。在記憶體中,字元串通常占用比所說明的大小多一個位元組的空間,由于第一個位置是一個包括這個數組大小的位元組。您能夠使用索引值來訪問字元串的字元,MyName[1]能夠得到MyName的第一個字元‘F‘。

您能夠使用Delphi豐富的運算符、過程和函數來處理字元串型的變量和屬性。以下介紹幾個經常使用的運算符和Delphi過程或函數:

Concat和(+)功能同樣,都能夠将多個字元串組合在一起,建立一個較大的字元串;Copy會傳回一個字元串中的子字元串;Delete在一個字元串中從一個指定位置起删除一定數目的字元;Insert在一個字元串中插入一個字元串;Length傳回字元串的長度;Pos傳回一個子字元串在一個字元串中的位置,即索引值。

2.1.9.5 集合類型

集合類型是一群同樣類型元素的組合,這些類型必須是有限類型如整形、布爾型、字元型、枚舉型和子界型。在檢查一個值是否屬于一個特定集合時,集合類型非常實用。以下的例程能夠說明集合類型的使用方法:

在視窗上增加一個編輯框和一個button,清除編輯框中的文字,在其上加上Caption為“輸入元音”的标簽Label,并在編輯框的下方增加一個空的标簽,将button的Default屬性改為True,建立button的事件處理步驟例如以下:

procedure TForm1.Button1Click(Sender:TObject);

Tvowels=set

of Char;

Vowels:TVowels;

Vowels :=

[‘a‘,‘e‘,‘i‘,‘o‘,‘u‘];

if Edit1.Text[1] in Vowels then

Lable2.Caption := ‘是元音‘;

Lable2.Caption := ‘請再試‘;

運作這個程式,在編輯框中輸入字母,表達式Edit1.Text[1] in

Vowels的結果是布爾型的,in是運算符,用來推斷字母是否存在于集合中。輸入的判别結果會顯示在編輯框的下方。以上就用到了集合類型TVowels。

2.1.9.6 記錄類型

記錄是您的程式能夠成組訪問的一群資料的集合。以下的例程說明了一個記錄類型的使用方法:

TEmployee=record

Name : string[20];

YearHired:1990..2000;

Salsry: Double;

Position: string[20];

記錄包括能夠儲存資料的域,每個域有一個資料類型。上文的記錄TEmployee類型就含有四個域。您能夠用以下的方式說明記錄型的變量:

NewEmployee,PromotedEmployee:TEmployee;

用例如以下的方法能夠訪問記錄的單域:

NewEmployee.Salary := 1000;

編寫例如以下的語句能夠給整個記錄指派:

with PromotedEmployee do

Name :=‘‘;

YearHired :=

1993;

Salary := 2000.00

Position := ‘editor‘;

您的程式能夠将記錄當成單一實體來操作:

PromptEmployee := NewEmployee;

以上介紹了使用者經常使用的自己定義類型。在Delphi的程式設計中,對象是非常重要的使用者自己定義資料類型。象記錄一樣,對象是結構化的資料類型,它包括資料的域(Field),也包括作為方法的過程和函數。在Delphi中,當您向視窗中增加一個部件,也就是向視窗對象中增加了一個域;每個部件也是對象,每當您建立一個事件處理過程使得部件能夠響應一個事件時,您即自己主動地在視窗中增加了一個方法。在本章第2節中,将具體講述Delphi面向對象程式設計的方法和技巧。

2.1.10 Object Pascal的庫單元Unit

Units是常量、變量、資料類型、過程和函數的集合,并且能夠被多個應用程式所共享。Delphi已經擁有很多提前定義的程式庫單元可供您建立您的程式庫單元使用。Delphi的Visual

Component Library由多個程式庫單元組成,它們說明了對象、部件以供您的應用程式用來設計使用者界面。比如,當您在視窗中增加一個Check

Box時,Delphi自己主動在您的程式庫單元中增加了Stdctrls庫單元,由于TCheckBox部件是在StdCtrls庫單元中說明的。

當您設計您的視窗時,Delphi自己主動建立一個和您的視窗有關的庫單元。您的庫單元不必都和視窗有關,也能夠使用提前定義的僅僅包括數學運算函數的庫單元,或是自行編寫數學函數庫單元。在一個庫單元中全部的說明都互相有關系,比如,CDialogs程式庫單元包括了在您的應用程式中使用的普通對話框的全部說明。

2.1.10.1 Object Pascal程式庫單元的結構

無論一個庫單元是否和一個視窗有關,庫單元的結構都是同樣的。其結構例如以下:

unit &lt;庫單元名稱&gt;

interface

uses &lt;選擇性的庫單元清單&gt;

{公有說明}

{私有說明}

{過程和函數的運作部分}

initialization {選擇性的}

{選擇性的初始化程式}

end.

2.1.10.2 程式庫單元的接口部分

interface是庫單元的接口部分,它決定了本庫單元對其它不論什麼庫單元或程式的可見(可訪問)部分。您能夠在接口部分說明變量、常量、資料類型、過程和函數等等。Delphi在您設計視窗的庫單元中,将視窗資料類型、視窗變量和事件處理過程都說明在這一部分。

interface标志庫單元接口部分的開始。在interface中的說明對要使用這些說明的其它庫單元或應用程式是可見的。一個庫單元能夠使用其它Unit的說明,僅僅須要在uses子句中指明那些庫單元就可以。比如,您在庫單元A中編敲代碼代碼,且您想調用UnitB于interface部分說明的程式。您能夠把庫單元B的名稱增加到A的interface部分的uses子句中,則不論什麼A中的程式都能夠調用B中說明的程式。并且,假設B中interface部分的uses子句中出現C庫單元,盡管A中未曾出現C,A同樣能夠調用B、C庫單元在interface中說明的程式。但假設B出如今A的interface部分的uses子句中,那麼庫單元A便不能出如今B的interface的uses子句中。由于這樣會産生對庫單元的循環訪問。當試圖編譯時,會産生出現錯誤資訊。

2.1.10.3 程式庫單元的實作部分

實作部分implementation中包括interface中說明的過程、函數、事件處理過程的具體實作程式代碼。這一部分能夠有自己的額外說明,但這些說明是私有的,外部程式不能調用這些說明。在interface中說明的函數實體必須在implementation部分出現,能夠使用标題簡寫:僅僅輸入procedure或function保留字,後面跟過程或函數的名稱就可以,其後則是程式的實作部分了。假設您在implementation部分說明不論什麼常式,其标題并未出如今interface部分,則必須寫全其标題部分。

在implementation部分的uses子句中指定的庫單元,僅僅供給本庫單元的程式使用其interface中說明的程式。其它使用本庫單元的庫單元,不能訪問這些在implementation的udes子句中庫單元的說明,由于在implementation後進行的庫單元包括是私有的。是以上例中,假設C出如今B的implementation部分,則A不能使用C的公有部分,除非C出如今A的uses子句中。在implementation中出現的循環訪問是Delphi所同意的,假設A的implemetation的uses子句中出現B,則B的implementation部分也能夠出現A。

2.1.10.4 程式庫單元的初始化部分

初始化目前庫單元所使用的資料,或是通過interface部分将資料提供給其它應用程式、庫單元使用時,您能夠在庫單元中增加一個initialization部分,在庫單元的end前加上您的初始化語句。當一個應用程式使用一個庫單元時,在庫單元中的initialization部分會先于其它的代碼運作。假設一個應用程式使用了多個庫單元,則每個庫單元的初始化部分都會在全部的程式代碼前運作。

2.1.10.5 使用Delphi的可視化部件及其庫單元

當您在視窗中增加可視化部件時,假設該部件在可視化部件庫中,Delphi會在您的庫單元的interface部分的uses子句中自己主動加上須要使用的庫單元名稱。但有些對象在Delphi的環境中并沒有可視化部件存在,比如,您想在庫單元中增加一個提前定義的資訊框,則您必須把MsgDlg庫單元增加您的uses子句中。假設您要使用TPrinter對象的話,必須将Printer庫單元增加uses子句中。在線上幫助中能夠查到對象所屬的提前定義庫單元。

要使用在其它庫單元中說明的函數,應在函數的前面加上這一庫單元的名稱,并用‘.’号隔開。比如,要在Unit2中使用Unit1中說明的Calculate函數,應使用以下的方法:

Number := Unit1.Calculate(10);

您能夠在不論什麼辨別符如屬性、常量、變量、資料類型、函數等之前加上庫單元的名稱。您能夠在自由地在不論什麼Delphi庫單元中增加程式代碼,但不要改變由Delphi生成的程式。

2.1.10.6 建立與視窗無關的新庫單元

假設您想在project中建立一個和不論什麼視窗無關的新庫單元,能夠現選用File|New

Unit。這時一個新的庫單元增加了project,新庫單元的代碼例如以下:

unit Unit2;

Delphi将依據您的project中的檔案數目為您的庫單元選擇名稱,您能夠在程式骨架間增加您的程式代碼。

當編譯您的project時,這個新增加的庫單元會被編譯為一個具有.DCU字尾的檔案。這個新生成的檔案是連結到project的可運作檔案上的機器代碼。

2.1.10.7 将庫單元增加project

将庫單元增加project是比較簡單的。無論是您自己建立的庫單元還是Delphi建立的與視窗有關的庫單元,假設已經完畢,則先打開您想增加庫單元的project(能夠用Open

Project打開project);再選用File|Open File,然後選擇您想增加的源程式(.PAS檔案),并選擇OK就可以。則庫單元被增加到應用程式中。

2.2 用Delphi的對象進行程式設計

Delphi是基于面向對象程式設計的先進開發環境。面向對象的程式設計(OOP)是結構化語言的自然延伸。OOP的先程序式設計方法,會産生一個清晰而又easy擴充及維護的程式。一旦您為您的程式建立了一個對象,您和其它的程式猿能夠在其它的程式中使用這個對象,全然不必又一次編制繁複的代碼。對象的反複使用能夠大大地節省開發時間,切實地提高您和其它人的工作效率。

2.2.1 什麼是對象

一個對象是一個資料類型。對象就象記錄一樣,是一種資料結構。按最簡單的了解,我們能夠将對象了解成一個記錄。但實際上,對象是一種定義不确切的術語,它經常使用來定義抽象的事務,是構成應用程式的項目,其内涵遠比記錄要豐富。在本書中,對象可被了解為可視化部件如button、标簽、表等。

了解對象,最關鍵的是掌握對象的特性。一個對象,其最突出的特征有三個:封裝性、繼承性、多态性。

2.2.1.1 對象的封裝性

對對象最基本的了解是把資料和代碼組合在同一個結構中,這就是對象的封裝特性。将對象的資料域封閉在對象的内部,使得外部程式必需并且僅僅能使用正确的方法才幹對要讀寫的資料域進行訪問。封裝性意味着資料和代碼一起出如今同一結構中,假設須要的話,能夠在資料周圍砌上“圍牆”,僅僅實用對象類的方法才幹在“圍牆”上打開缺口。

2.2.1.2 對象的繼承性

繼承性的含義直接并且顯然。它是指把一個新的對象定義成為已存在對象的後代;新對象繼承了舊類的一切東西。在往新對象中增加不論什麼新内容曾經,父類的每個字段和方法都已存在于子類中,父類是建立子類的基石。

2.2.1.3 對象的多态性

多态性是在對象體系中把設想和實作分開的手段。假設說繼承性是系統的布局手段,多态性就是其功能實作的方法。多态性意味着某種概括的動作能夠由特定的方式來實作,這取決于運作該動作的對象。多态性同意以相似的方式處理類體系中相似的對象。依據特定的任務,一個應用程式被分解成很多對象,多态性把進階設計處理的設想如新對象的建立、對象在螢幕上的重顯、程式運作的其它抽象描寫叙述等,留給知道該怎樣完美的處理它們的對象去實作。

2.2.1.4 通過Delphi執行個體了解對象

讓我們結合Delphi的執行個體讨論對象的概念:

當您要建立一個新project時,Delphi

将顯示一個視窗作為設計的基礎。在程式編輯器中,Delphi将這個視窗說明為一個新的對象類型,并同一時候在與視窗相關聯的庫單元中生成了建立這個新視窗對象的程式代碼。

unit Unit1;

uses SysUtils, Windows, Messages, Classes,

Graphics, Controls, Forms, Dialogs;

TForm1 = class(TForm)

{視窗的類型說明開始}

{ Private declarations }

{ Public declarations }

end; {視窗的類型說明結束}

Form1:

TForm1; {說明一個視窗變量}

{$R *.DFM}

新的視窗類型是TForm1,它是從TForm繼承下來的一個對象。它具有對象的特征:含有域或方法。由于您未給視窗增加不論什麼部件,是以它僅僅有從TForm類中繼承的域和方法,在視窗對象的類型說明中,您是看不到不論什麼域、方法的說明的。Form1稱為TForm1類型的執行個體(instance)。您能夠說明多個對象類型的執行個體,比如在多文檔界面(MDI)中管理多個子視窗時就要進行這樣的說明。每個執行個體都有自己的說明,但全部的執行個體卻共用同樣的代碼。

假設您向視窗中增加了一個button部件,并對這個button建立了一個OnClick事件處理過程。再檢視Unit1的源程式,會發現TForm1的類型說明部分例如以下:

Button1: TButton;

Button1Click(Sender: TObject);

如今TForm1對象有了一個名為Button1的域:它是您在視窗中增加的button。TButton是一個對象類型,Button1是Tbutton的一個執行個體。它被TForm1對象所包括,作為它的資料域。每當您在視窗中增加一個部件時,部件的名稱就會作為TFom1的域增加到類型說明中來。在Delphi中,您所編寫的事件處理過程都是視窗對象的方法。每當您建立一個事件處理過程,就會在視窗的對象類型中說明一個方法。

當您使用Object Inspector來改變對象(部件)的名稱時,這個名稱的改變會反映到程式中。比如,在Object

Inspector中将Form1的Name屬性命名為ColorBox,您會發如今類型說明部分,會将前文的TForm1改為:

TColorBox=class(TForm);

并且在變量說明部分,會說明ColorBox為TColorBox類型的變量,由Delphi自己主動産生的事件處理過程名稱會自己主動改為TColorBox.Button1Click;但您自行編寫的實作部分的代碼卻不會被自己主動改動。是以,假設您在改變Name屬性前編寫了程式,則您必須将事件處理過程中的對象名稱進行改變。是以,原先的Form1.Color要改為ColorBox.Color。

2.2.2 從一個對象中繼承資料和方法

前面的TForm1類型是非常簡單的,由于它僅僅含有域Button1和方法Button1Click。可是在這個視窗上,您能夠改變視窗的大小、增加或删除視窗的最大最小化button,或設定這個視窗為MDI界面。對于一個僅僅包括一個域和方法的對象來講,您并沒有看到顯式的支援程式。在視窗上單擊滑鼠或用Object

Inspector的上端的Object

Selector選中Form1對象,按動F1查閱它的線上幫助,您會在Properties和Method中找到它的繼承到的全部屬性和方法。這些是在TForm類型中說明的,TForm1是TForm的子類,直接繼承了它全部的域、方法、屬性和事件。比如視窗的顔色屬性Color就是在TForm中說明的。當您在project中增加一個新視窗時,就等于增加了一個基本模型。通過不斷地在視窗中增加部件,您就自行定義了一個新的視窗。要自己定義不論什麼對象,您都将從已經存在的對象中繼承域和方法,建立一個該種對象的子類。比如對象TForm1就被說明為對象TForm的子類,擁有一個視窗部件的基本屬性或方法。僅僅有當您在視窗中增加了部件或編寫了事件處理過程時,Form1才成為您自己的類型。

一個比較特殊的對象是從一個範圍較廣或較一般的對象中繼承下來的,它是這個特别對象的祖先,這個對象則稱為祖先的後代。一個對象僅僅能有一個直接的祖先,可是它能夠有很多後代。TForm是TForm1類型的祖先,全部的視窗對象都是TForm的後代。

用F1查閱視窗的線上幫助時,您會發現TForm被稱為component(部件)。這是由于全部的部件都是對象。

在這個結構中全部的部件都是對象。部件類型TComponent從TObject類型中繼承資料和程式代碼,并具有額外的能夠用作特殊用途的屬性、方法、事件,是以部件能夠直接和使用者打交道,記錄它的狀态并存貯到檔案裡等等。控制類型TControl從TComponent中繼承而來,又增加了新的功能,如它能夠顯示一個對象。在上圖中,盡管TCheckBox不是直接由TObject繼承來的,可是它仍然有不論什麼對象所擁有的屬性,由于在VCL結構中,TCheckBox終究還是從TObject

中繼承了全部功能的特殊對象,但它還有些自行定義的獨到的功能,如能夠選擇記錄狀态等。

2.2.3 對象的範圍

2.2.3.1 關于對象的範圍

一個對象的範圍決定了它的資料域、屬性值、方法的活動範圍和訪問範圍。在一個對象的說明部分說明的資料域、屬性值、方法都僅僅是在這個對象的範圍中,并且僅僅有這個對象和它的後代才幹擁有它們。盡管這些方法的實際程式代碼可能是在這個對象之外的程式庫單元中,但這些方法仍然在這個對象的範圍内,由于它們是在這個對象的說明部分中說明的。

當您在一個對象的事件處理過程中編敲代碼代碼來訪問這個對象的屬性值、方法或域時,您不須要在這些辨別符之前加上這個對象變量的名稱。比如,假設您在一個新視窗上增加一個button和一個編輯框,并為這個button編寫OnClick事件處理過程:

procedure TForm1.Button1Click(Sender:Tobject);

Color

:=clFuchsia;

Edit1.Color :=clLime;

當中的第一行語句是為整個視窗Form1着色。您也能夠編寫例如以下:

Form1.Color :=clFuchsia;

但您能夠不必加上Form1.,由于Button1Click方法是在TForm1對象的範圍裡。當您在一個對象的範圍中時,您能夠省略全部這個對象中的屬性值、方法、域之前的對象辨別符。可是當您編寫第二個語句改變編輯框的底色時,由于此時您想訪問的是TEdit1對象的Color屬性,而不是TForm1類型的,是以您須要通過在屬性前面加上編輯框的名稱來指明Color屬性值的範圍。假設不指明,Delphi會象第一個語句一樣,将視窗的顔色變成綠色。由于Edit1部件是在視窗中的,它是視窗的一個資料域,是以您同樣不必指明其從屬關系。

假設Edit1是在其它視窗中,那麼您須要在編輯框之前加上這個船體對象的名稱了。比如,假設Edit1是在Form2之中,那它是Form2說明的一個資料域,并位于Form2的範圍中,那麼您須要将第二句改為:

Form2.Edit1.Color := clLime;

并且須要把Unit2增加Unit1的uses子句中。

一個對象的範圍擴充到這個對象的全部後代。TForm的全部屬性值、方法和事件都在TForm1的範圍中,由于TForm1是TForm的後代。您的應用程式不能說明和祖先的資料域重名的類型、變量等。假設Delphi顯示了一個辨別符被反複定義的資訊,就有可能是一個資料域和其祖先對象(比如TForm)的一個資料域有了同樣的名稱。能夠嘗試改變這個辨別符的名稱。

2.2.3.2 重載一個方法

您能夠重載(Override)一個方法。通過在後代對象中說明一個與祖先對象重名的方法,就能夠重載一個方法。假設想使這種方法在後代對象中作和祖先對象中一樣的工作可是使用不同的方式時,您就能夠重載這種方法。Delphi不推薦您經常重載方法,除非您想建立一個新的部件。重載一個方法,Delphi編譯器不會給出錯誤或警告提示資訊。

2.2.4 對象公有域和私有域的說明

當使用Delphi的環境來建立應用程式時,您能夠在一個TForm的後代對象中增加資料域和方法,也能夠通過直接改動對象類型說明的方法來為一個對象加上域和方法,而不是把一個部件增加視窗或事件處理過程中。

您能夠在對象的Public或Private部分增加新的資料域和方法。Public和Private是Object

Pascal的保留字。當您在project中增加新的視窗時,Delphi開始建立這個新視窗對象。每個新的對象都包括public和private訓示,以便您在代碼中增加資料域和方法。在public部分中說明其它庫單元中對象的方法也能夠訪問的資料域或方法。在private部分的說明有訪問的限制。假設您在private中說明域和方法,那麼它在說明這個對象的庫單元外是不透明的,并且不能被訪問。private中能夠說明僅僅能被本庫單元方法訪問的資料域和本庫單元對象訪問的方法。過程或函數的程式代碼能夠放在庫單元的implementation部分。

2.2.5 訪問對象的域和方法

當您想要改變一個視窗對象的一個域的某個屬性,或是調用它的一個方法時,您必須在這個屬性名稱或調用方法之前加上這個對象的名稱。比如,假設您的視窗上有一個編輯框部件,而您須要在運作中改變它的Text屬性,須要編寫下列的代碼:

Edit1.Text := ‘Welcome to Delphi‘;

同樣,清除編輯框部件中選中的文本,能夠調用TEdit部件的對應方法:

Edit1.ClearSelection;

假設您想改變一個視窗對象中一個對象域的多個屬性或調用多個方法時,使用with語句能夠簡化您的程式。with語句在對象中能夠和在記錄中一樣友善地使用。以下的事件處理過程在響應OnClick事件時,會對一個清單框作多個調整:

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);

假設使用了With語句,則程式例如以下:

with

(ListBox1) do

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);

使用with語句,您不必在每個屬性或方法前加上ListBox1辨別符,在With語句之内,全部的屬性或調用方法對于ListBox這個對象而言都是在它的範圍内的。

2.2.6 對象變量的指派

假設兩個變量類型同樣或相容,您能夠把當中一個對象變量賦給還有一個對象變量。比如,對象TForm1和TForm2都是從TForm繼承下來的類型,并且Form1和Form2已被說明過,那麼您能夠把Form1賦給Form2:

Form2 :=Form1;

僅僅要指派的對象變量是被指派的對象變量的祖先類型,您就能夠将一個對象變量賦給還有一個對象變量。比如,以下是一個TDataForm的類型說明,在變量說明部分一共說明了兩個變量:AForm和DataForm。

TDataForm = class(TForm)

Button1:TButton;

Edit1:TEdit;

DataGrid1:TDataGrid;

Database1:TDatabase;

TableSet1:TTableSet;

VisibleSession1:TVisibleSession;

{私有域說明}

{公有域說明}

AForm:TForm;

DataForm:TDataForm;

由于TDataForm是TForm類型的後代,是以Dataform是AForm的後代,是以以下的指派語句是合法的:

AForm :=DataForm;

這一點在Delphi中是極為重要的。讓我們來看一下應用程式調用事件處理過程的過程,以下是一個button部件的OnClick事件處理過程:

您能夠看到TObject類在Delphi的Visual Component

Library的頂部,這就意味着全部的Delphi對象都是TObject的後代。由于Sender是TObject類型,是以不論什麼對象都能夠指派給它。盡管您沒有看見指派的程式代碼,但事實上發生事件的部件或控制部件已經賦給Sender了,這就是說Sender的值是響應發生事件的部件或控制部件的。

您能夠使用保留字is來測試Sender以便找到調用這個事件處理過程的部件或控制部件的類型。Delphi中的一個顯示drag-and-drop的DRAGDROP.DPRproject。載入它,能夠查閱到DROPFONT.PAS庫單元的代碼,在Memo1DragOver方法中檢查了一個對象變量的類型。在這樣的情形下,參數是Source而不是Sender。

procrdure TForm1.Memo1DragOver(SenderSource:TObject;X,Y:integer;

State:TDragState;var Accept:Boolean);

Accept :=Source is

TLabel;

Source參數也是TObject類型,Source被指派為那個被拖曳的對象。用Memo1DragOver方法的目的是確定僅僅有标簽能夠被拖曳。Accept是布爾型參數,假設Accept為True,那麼使用者選擇的部件能夠被拖曳;反之當Accept的值為False時,使用者就不能夠拖曳選擇控制部件。is保留字檢查Source是否TLabel的類型,是以Accept僅僅有在使用者拖曳一個标簽時才為真,并作為變參輸出到函數之外。

以下的drag-and-drop展示的Memo1DragDrop事件處理過程中也使用了Source參數。這種方法是為了把Memo部件的字型改變成和放入這個備注控制部件的标簽一樣的字型:

procedure TForm1.Memo1DragDrop(SenderSource:TObject;

X,Y:Integer);

Memo1.Font := (Source as TLabel).Font;

當您在這個事件處理過程中編寫指派語句時,開發者并不知道使用者會放入哪一個标簽,僅僅有通過參考這個标簽的名稱(Source as

TLabel)使用者才幹知道,并把标簽類型賦給Memo1.TFont。Source包括了使用者拖放控制部件的名稱,僅僅有當Source是一個标簽時,這個事件處理過程才同意這個指派發生。

2.2.7 建立非可視化對象

您在Delphi中使用的大部分對象都是您在設計和運作期間能夠看見的部件,比如編輯框、button等;一些部件,如通用對話框(Common dialog

box)等,在設計時看不見,而在運作時能夠看見;另外有些部件,比如計時器(Timer)、資料源(Data

Source)部件等,在程式的運作期間沒有不論什麼可視化的顯示,但您卻能夠在您的應用程式中使用它們。

2.2.7.1說明一個非可視化對象

以下,通過一個簡單的樣例講述怎樣建立自己的非可視化對象:

您能夠用例如以下的方法,建立一個自己的TEmployee非可視化對象:

Temployee = class(TObject);

Name := String[25];

Title := String[25];

HourlyPayRate : Double;

function

CalculatePayAmount:Double;

在這樣的情況下,TEmployee從TObject繼承下來,且包括三個域和一個方法。把您建立的類型說明放在庫單元中的說明部分,并和視窗說明放在一起。在這個程式庫單元的變量說明部分,說明一個新類型的變量:

Employee : TEmployee;

2.2.7.2用Create方法建立對象執行個體

TEmployee僅僅是一個對象類型。除非通過一個構造函數的調用進而被執行個體取代或建立,否則一個對象并不存儲在記憶體中。構造函數是一個方法,它為新對象配置記憶體并且指向這個新的對象。這個新的對象也被稱為這個對象類型的一個執行個體。

建立一個對象的執行個體,須要調用Create方法,然後構造函數把這個執行個體賦給一個變量。假設您想說明一個TEmployee類型的執行個體,在您訪問這個對象的不論什麼域之前,您的程式代碼必須調用Create。

Employee := TEmployee.Create;

Create方法并沒有在TEmployee類型中說明,它繼承自TObject類型。由于TEmployee是TObject的子類,是以它能夠調用Create方法而建立一個TEmployee執行個體。然後把它賦給Employee變量。在建立了一個這樣的對象後,您就能夠象使用其它的Delphi對象一樣訪問Employee對象了。

2.2.7.3 撤銷對象

當您使用完對象後,您應該及時撤銷它,以便把這個對象占用的記憶體釋放出來。您能夠通過調用一個登出方法來撤銷您的對象,它會釋放配置設定給這個對象的記憶體。

Delphi的登出方法有兩個:Destroy和Free。Delphi建議使用Free,由于它比Destroy更為安全,同一時候調用Free會生成效率更高的代碼。

您能夠用下列的語句釋放用完的Employee對象:

Employee.Free;

和Create方法一樣,Free方法也是TEmployee從TObject中繼承過來的。把您的登出放在try…finally程式子產品的finally部分,而把對象的程式代碼放在try部分是程式設計的好習慣。這樣,即使您的程式代碼在使用對象時發生了異常事件,也會確定您為這個對象配置設定的記憶體會被釋放。關于異常處理和try…finally程式子產品的資訊以及建立非可視化對象的樣例,在後文中還将細緻講述。

<b>用</b><b>hook</b><b>實作</b><b>dll</b><b>注入具體解釋</b><b></b>

須要一個用來注入的dll(inject.dll)及一個調用程式(caller.exe)

流程:

caller.exe

procedure TestHook;

var pwnd,hChild, hwndInject

:hwnd;

msg:tmsg;

//通過視窗标題用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);

//調用 inject.dll的SetInjectHook方法

SetInjectHook(dwThreadID);

//等待消息傳回

getmessage(msg,0,0,0);

//找到注入的視窗

hwndInject:= findwindow(nil,‘InjectForm‘);

//發送控制消息,将目标視窗的句柄作為wparam,控制參數以lparam傳入

sendMessage( hwndInject,

wm_app,hChild,integer(true));

//關閉注入的視窗

sendMessage(

hwndInject,wm_close,0,0);

//等待視窗關閉

sleep(500);

//檢查是否成功關閉

assert(not iswindow( hwndInject));

//去掉挂鈎

setDipsHook(0);

//以下說明 Inject.dll的SetInjectHook的具體操作

在全局定義以下變量

g_hhook :Hhook=0;

g_dwThreadidInject :dword=0;

g_hInjectfrm:hwnd;

function SetInjectHook(dwThreadid:DWORD):boolean;

result := false;

//假設線程标志為0則用于去掉鈎子,否則進行動态庫注入

dwThreadid&lt;&gt;0 then

assert(g_hhook=0);

//儲存目前線程的ID到 g_dwThreadidInject

g_dwThreadidInject :=

getCurrentThreadid;

//下一個GetMessage的鈎子到目标線程

//GetMsgProc是在以下定義的一個函數,在第一次調用時将自己定義的form在目标線程中建立出來

//這樣就能通過這個自己定義的form對目标線程進行程序内控制了

g_hhook :=

setWindowsHookEx(wh_getMessage,GetMsgProc,hInstance,dwThreadid);

result

:= g_hhook &lt;&gt; null;

if result then

//發一個空的資訊以便于立即建立這個自己定義form

result := postThreadMessage(dwThreadid,

wm_Null,0,0);

//等待半秒鐘,以保證調用者能夠找到這個剛建立的form

assert(g_hhook&lt;&gt;0);

//去掉鈎子

:= unHookWindowsHookEx(g_hhook);

g_Hhook := 0;

//定義一個全局的是否第一個消息的标志

fFirstTime:boolean = true;

//這個函數用于在收到第一個消息時建立自己定義視窗,以便于遠端控制

function GetMsgProc(code: Integer;

wparam: WPARAM; lparam: LPARAM): LRESULT; stdcall;

//假設是第一次

if fFirstTime then

fFirstTime := false;

//建立視窗

InjectFrm := TinjectFrm.create(nil);

//儲存視窗句柄

g_hInjectfrm

:= InjectFrm.handle;

//調用預設處理,這一句可不能忘記

result :=

callNexthookEx(g_hhook,code,wparam,lparam);

<b>《</b><b>COM </b><b>原理與應用》學習筆記</b><b> - </b><b>第一部分</b><b></b>

COM<b>原理</b><b></b>

⊙ 第一章 概述

===================================================

COM 是什麼

---------------------------------------------------

COM 是由 Microsoft 提出的元件标準,它不僅定義了元件程式之間進行互動的标準,并且也提供了元件程式運作所需的環境。在 COM

标準中,一個元件程式也被稱為一個子產品,它能夠是一個動态連結庫,被稱為程序内元件(in-process component);也能夠是一個可運作程式(即 EXE

程式),被稱作程序外元件(out-of-process component)。一個元件程式能夠包括一個或多個元件對象,由于 COM

是以對象為基本單元的模型,是以在程式與程式之間進行通信時,通信的兩方應該是元件對象,也叫做 COM 對象,而元件程式(或稱作 COM 程式)是提供 COM

對象的代碼載體。

COM 對象不同于一般面向對象語言(如 C++ 語言)中的對象概念,COM 對象是建立在二進制可運作代碼級的基礎上,而 C++

等語言中的對象是建立在源碼級基礎上的,是以 COM 對象是語言無關的。這一特性使用不同程式設計語言開發的元件對象進行互動成為可能。

COM 對象與接口

相似于 C++

中對象的概念,對象是某個類(class)的一個執行個體;而類則是一組相關的資料和功能組合在一起的一個定義。使用對象的應用(或還有一個對象)稱為客戶,有時也稱為對象的使用者。

接口是一組邏輯上相關的函數集合,其函數也被稱為接口成員函數。依照習慣,接口名常是以“I”為字首。對象通過接口成員函數為客戶提供各種形式的服務。

在 COM 模型中,對象本身對于客戶來說是不可見的,客戶請求服務時,僅僅能通過接口進行。每個接口都由一個 128

位的全局唯一辨別符(GUID,Global Unique Identifier)來辨別。客戶通過 GUID

來獲得接口的指針,再通過接口指針,客戶就能夠調用其對應的成員函數。

<b>COM </b><b>原理與應用》學習筆記</b><b> - </b><b>第一部分</b><b></b>

與接口相似,每個元件也用一個 128 位 GUID 來辨別,稱為 CLSID(class identifer,類辨別符或類 ID),用 CLSID

辨別對象能夠保證(機率意義上)在全球範圍内的唯一性。實際上,客戶成功地建立對象後,它得到的是一個指向對象某個接口的指針,由于 COM

對象至少實作一個接口(沒有接口的 COM 對象是沒有意義的),是以客戶就能夠調用該接口提供的全部服務。依據 COM 規範,一個 COM

對象假設實作了多個接口,則能夠從某個接口得到該對象的随意其它接口。從這個過程我們也能夠看出,客戶與 COM

對象僅僅通過接口打交道,對象對于客戶來說僅僅是一組接口。

COM 程序模型

COM

所提供的服務元件對象在實作時有兩種程序模型:程序内對象和程序外對象。假設是程序内對象,則它在客戶程序空間中運作;假設是程序外對象,則它運作在同機器上的還有一個程序空間或者在遠端機器的空間。

程序内服務程式:

服務程式被載入到客戶的程序空間,在 Windows 環境下,通常服務程式的代碼以動态連接配接庫(DLL)的形式實作。

本地服務程式:

服務程式與客戶程式運作在同一台機器上,服務程式是一個獨立的應用程式,通常它是一個 EXE 檔案。

遠端服務程式:

服務程式運作在與客戶不同的機器上,它既能夠是一個 DLL 子產品,也能夠是一個 EXE 檔案。假設遠端服務程式是以 DLL

形式實作的話,則遠端機器會建立一個代理程序。

盡管 COM 對象有不同的程序模型,但這樣的差别對于客戶程式來說是透明的,是以客戶程式在使用元件對象時能夠無論這樣的差别的存在,僅僅要遵照 COM

規範就可以。然而,在實作 COM

對象時,還是應該謹慎選擇程序模型。程序内模型的長處是效率高,但元件不穩定會引起客戶程序崩潰,是以元件可能會危及客戶;(savetime

注:這裡有點問題,假設元件不穩定,程序外模型也同樣會出問題,可能是由于程序内元件和客戶同處一個位址空間,出現沖突的可能性比較大?)程序外模型的長處是穩定性好,元件程序不會危及客戶程式,一個元件程序能夠為多個客戶程序提供服務,但程序外元件開銷大,并且調用效率相對低一點。

COM 可重用性

由于 COM 标準是建立在二進制代碼級的,是以 COM 對象的可重用性與一般的面向對象語言如 C++ 中對象的重用過程不同。對于 COM

對象的客戶程式來說,它僅僅是通過接口使用對象提供的服務,它并不知道對象内部的實作過程,是以,元件對象的重用性可建立在元件對象的行為方式上,而不是具體實作上,這是建立重用的關鍵。COM

用兩種機制實作對象的重用。我們假定有兩個 COM 對象,對象1 希望能重用對象2 的功能,我們把對象1 稱為外部對象,對象2 稱為内部對象。

(1)包容方式。

對象1 包括了對象2,當對象1 須要用到對象2 的功能時,它能夠簡單地把實作交給對象2 來完畢,盡管對象1 和對象2 支援同樣的接口,但對象1

在實作接口時實際上調用了對象2 的實作。

(2)聚合方式。

對象1 僅僅需簡單地把對象2 的接口遞交給客戶就可以,對象1 并沒有實作對象2 的接口,但它把對象2

的接口也暴露給客戶程式,而客戶程式并不知道内部對象2 的存在。

⊙ 第二章 COM 對象模型

全局唯一辨別符 GUID

COM 規範採用了 128 位全局唯一辨別符 GUID 來辨別對象和接口,這是一個随機數,并不須要專門機構進行配置設定和管理。由于 GUID

是個随機數,是以并不絕對保證唯一性,但發生辨別符相重的可能性非常小。從理論上講,假設一台機器每秒産生 10000000 個 GUID,則能夠保證(機率意義上)的

3240 年不反複)。

GUID 在 C/C++ 中能夠用這樣的結構來描寫叙述:

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

函數能夠産生 GUID:

HRESULT CoCreateGuid(GUID *pguid);

假設建立 GUID 成功,則函數傳回 S_OK,并且 pguid 将指向所得的 GUID 值。

COM 對象

在 COM 規範中,并沒有對 COM 對象進行嚴格的定義,但 COM 提供的是面向對象的元件模型,COM

元件提供給客戶的是以對象形式封裝起來的實體。客戶程式與 COM 程式進行互動的實體是 COM

對象,它并不關心元件模型的名稱和位置(即位置透明性),但它必須知道自己在與哪個 COM 對象進行互動。

COM 接口

從技術上講,接口是包括了一組函數的資料結構,通過這組資料結構,客戶代碼能夠調用元件對象的功能。接口定義了一組成員函數,這組成員函數是元件對象暴露出來的全部資訊,客戶程式利用這些函數獲得元件對象的服務。

通常我們把接口函數表稱為虛函數表(vtable),指向 vtable 的指針為

pVtable。對于一個接口來說,它的虛函數表是确定的,是以接口的成員函數個數是不變的,并且成員函數的先後先後順序也是不變的;對于每個成員函數來說,其參數和傳回值也是确定的。在一個接口的定義中,全部這些資訊都必須在二進制一級确定,無論什麼語言,僅僅要能支援這樣的記憶體結構描寫叙述,就能夠使用接口。

接口指針 ----&gt; pVtable ----&gt; 指針函數1 -&gt; |----------|

m_Data1 指針函數2

-&gt; | 對象實作 |

m_Data2 指針函數3 -&gt; |----------|

每個接口成員函數的第一個參數為指向對象執行個體的指針(=this),這是由于接口本身并不獨立使用,它必須存在于某個 COM

對象上,是以該指針能夠提供對象執行個體的屬性資訊,在被調用時,接口能夠知道是對哪個 COM 對象在進行操作。

在接口成員函數中,字元串變量必須用 Unicode 字元指針,COM 規範要求使用 Unicode 字元,并且 COM 庫中提供的 COM API

函數也使用 Unicode 字元。是以假設在元件程式内部使用到了 ANSI

字元的話,則應該進行兩種字元表達的轉換。當然,在即建立元件程式又建立客戶程式的情況下,能夠使用自己定義的參數類型,僅僅要它們與 COM

所能識别的參數類型相容。

Visual C++ 提供兩種字元串的轉換:

namespace _com_util {

BSTR ConvertStringToBSTR(const char *pSrc)

throw(_com_error);

BSTR ConvertBSTRToString(BSTR pSrc) throw(_com_error);

}

BSTR 是雙位元組寬度字元串,它是最經常使用的自己主動化資料類型。

接口描寫叙述語言 IDL

COM 規範在採用 OSF 的 DCE 規範描寫叙述遠端調用接口 IDL (interface description

language,接口描寫叙述語言)的基礎上,進行擴充形成了 COM

接口的描寫叙述語言。接口描寫叙述語言提供了一種不依賴于不論什麼語言的接口的描寫叙述方法,是以,它能夠成為元件程式和客戶程式之間的共同語言。

COM 規範使用的 IDL 接口描寫叙述語言不僅可用于定義 COM

接口,同一時候還定義了一些經常使用的資料類型,也能夠描寫叙述自己定義的資料結構,對于接口成員函數,我們能夠定義每個參數的類型、輸入輸出特性,甚至支援可變長度的數組的描寫叙述。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);

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&amp; iid, void **ppv) = 0;

virtual ULONG _stdcall AddRef() = 0;

virutal ULONG _stdcall Release() = 0;

COM 對象的接口原則

COM 規範對 QueryInterface 函數設定了以下規則:

1. 對于同一個對象的不同接口指針,查詢得到的 IUnknown 接口必須全然同樣。也就是說,每個對象的 IUnknown

接口指針是唯一的。是以,對兩個接口指針,我們能夠通過推斷其查詢到的 IUnknown 接口是否相等來推斷它們是否指向同一個對象。

2. 接口自反性。對一個接口查詢其自身總應該成功,比方:

pIDictionary-&gt;QueryInterface(IID_Dictionary, ...) 應該傳回 S_OK。

3. 接口對稱性。假設從一個接口指針查詢到還有一個接口指針,則從第二個接口指針再回到第一個接口指針必然成功,比方:

pIDictionary-&gt;QueryInterface(IID_SpellCheck, (void

**)&amp;pISpellCheck);

假設查找成功的話,則再從 pISpellCheck 查回 IID_Dictionary 接口肯定成功。

4.

接口傳遞性。假設從第一個接口指針查詢到第二個接口指針,從第二個接口指針能夠查詢到第三個接口指針,則從第三個接口指針一定能夠查詢到第一個接口指針。

5. 接口查詢時間無關性。假設在某一個時刻能夠查詢到某一個接口指針,則以後不論什麼時間再查詢同樣的接口指針,一定能夠查詢成功。

總之,無論我們從哪個接口出發,我們總能夠到達不論什麼一個接口,并且我們也總能夠回到最初的那個接口。

⊙ 第三章 COM 的實作

COM 元件注冊資訊

目前機器上全部元件的資訊 HKEY_CLASS_ROOT/CLSID

程序内元件 HKEY_CLASS_ROOT/CLSID/guid/InprocServer32

程序外元件 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 也存在)

目前機器全部元件類别 HKEY_CLASS_ROOT/Component Categories

COM 提供兩個 API 函數 CLSIDFromProgID 和 ProgIDFromCLSID 轉換 ProgID 和 CLSID。

假設 COM 元件支援同樣一組接口,則能夠把它們分到同一類中,一個元件能夠被分到多個類中。比方全部的自己主動化對象都支援 IDispatch

接口,則能夠把它們歸成一類“Automation Objects”。類别資訊也用一個 GUID 來描寫叙述,稱為

CATID。元件類别最基本的用處在于客戶能夠高速發現機器上的特定類型的元件對象,否則的話,就必須檢查全部的元件對象,并把元件對象裝入到記憶體中執行個體化,然後依次詢問是否實作了必要的接口,如今使用了元件類别,就能夠節省查詢過程。

注冊 COM 元件

RegSrv32.exe 用于注冊一個程序内元件,它調用 DLL 的 DllRegisterServer 和 DllUnregisterServer

函數完畢元件程式的注冊和登出操作。假設操作成功傳回 TRUE,否則傳回 FALSE。

對于程序外元件程式,情形稍有不同,由于它自身是個可運作程式,并且它也不能提供入口函數供其它程式使用。是以,COM

規範中規定,支援自注冊的程序外元件必須支援兩個指令行參數 /RegServer 和

/UnregServer,以便完畢注冊和登出操作。指令行參數大寫和小寫無關,并且 “/” 能夠用 “-” 替代。假設操作成功,程式傳回 0,否則,傳回非 0

表示失敗。

類廠和 DllGetObjectClass 函數

類廠(class factory)是 COM 對象的生産基地,COM 庫通過類廠建立 COM 對象;對應每個 COM 類,有一個類廠專門用于該 COM

類的對象建立操作。類廠本身也是一個 COM 對象,它支援一個特殊的接口 IClassFactory:

class IClassFactory : public IUnknown

virtual HRESULT _stdcall

CreateInstance(IUnknown *pUnknownOuter,

const IID&amp; iid, void **ppv) =

0;

virtual HRESULT _stdcall LockServer(BOOL bLock) = 0;

CreateInstance 成員函數用于建立對應的 COM 對象。第一個參數 pUnknownOuter 用于對象類被聚合的情形,一般設定為

NULL;第二個參數 iid 是對象建立完畢後客戶應該得到的初始接口 IID;第三個參數 ppv 存放傳回的接口指針。

LockServer 成員函數用于控制元件的生存周期。

類廠對象是由 DLL 引出函數 DllGetClassObject 建立的:

HRESULT DllGetClassObject(const CLSID&amp; clsid, const IID&amp; iid, (void

**)ppv);

DllGetClassObject 函數的第一個參數為待建立對象的 CLSID。由于一個元件可能實作了多個 COM 對象類,是以在

DllGetClassObject 函數的參數中有必要指定 CLSID,以便建立正确的 class factory。另兩個參數 iid 和 ppv

分别指于指定接口 IID 和存放類廠接口指針。

COM 庫在接到對象建立的指令後,它要調用程序内元件的 DllGetClassObject 函數,由該函數建立類廠對象,并傳回類廠對象的接口指針。COM

庫或客戶一旦擁有類廠的接口指針,它們就能夠通過 IClassFactory 的成員函數 CreateInstance 建立對應的 COM 對象。

CoGetClassObject 函數

在 COM 庫中,有三個 API 可用于對象的建立,它們各自是 CoGetClassObject、CoCreateInstnace 和

CoCreateInstanceEx。通常情況下,客戶程式調用當中之中的一個完畢對象的建立,并傳回對象的初始接口指針。COM

庫與類廠也通過這三個函數進行互動。

HRESULT CoGetClassObject(const CLSID&amp; clsid, DWORD dwClsContext,

COSERVERINFO *pServerInfo, const IID&amp; iid, (void **)ppv);

CoGetClassObject 函數先找到由 clsid 指定的 COM 類的類廠,然後連接配接到類廠對象,假設須要的話,CoGetClassObject

函數裝入元件代碼。假設是程序内元件對象,則 CoGetClassObject 調用 DLL 子產品的 DllGetClassObject 引出函數,把參數

clsid、iid 和 ppv 傳給 DllGetClassObject 函數,并傳回類廠對象的接口指針。通常情況下 iid 為 IClassFactory

的辨別符 IID_IClassFactory。假設類廠對象還支援其它可用于建立操作的接口,也能夠使用其它的接口辨別符。比如,可請求 IClassFactory2

接口,以便在建立時,驗證使用者的許可證情況。IClassFactory2 接口是對 IClassFactory 的擴充,它加強了元件建立的安全性。

參數 dwClsContext 指定元件類别,能夠指定為程序内元件、程序外元件或者程序内控制對象(相似于程序外元件的代理對象,主要用于 OLE

技術)。參數 iid 和 ppv 分别對應于 DllGetClassObject 的參數,用于指定接口 IID 和存放類對象的接口指針。參數

pServerInfo 用于建立遠端對象時指定server資訊,在建立程序内元件對象或者本地程序外元件時,設定 NULL。

假設 CoGetClassObject 函數建立的類廠對象位于程序外元件,則情形要複雜得多。首先 CoGetClassObject

函數啟動元件程序,然後一直等待,直到元件程序把它支援的 COM 類對象的類廠注冊到 COM 中。于是 CoGetClassObject 函數把 COM

中對應的類廠資訊傳回。是以,元件外程序被 COM 庫啟動時(帶指令行參數“/Embedding”),它必須把所支援的 COM 類的類廠對象通過

CoRegisterClassObject 函數注冊到 COM 中,以便 COM 庫建立 COM 對象使用。當程序退出時,必須調用

CoRevokeClassObject 函數以便通知 COM 它所注冊的類廠對象不再有效。元件程式調用 CoRegisterClassObject 函數和

CoRevokeClassObject 函數必須配對,以保證 COM 資訊的一緻性。

CoCreateInstance /

CoCreateInstanceEx 函數

HRESULT CoCreateInstance(const CLSID&amp; clsid, IUnknown *pUnknownOuter,

DWORD dwClsContext, const IID&amp; iid, (void **)ppv);

CoCreateInstance 是一個被包裝過的輔助函數,在它的内部實際上也調用了 CoGetClassObject

函數。CoCreateInstance 的參數 clsid 和 dwClsContext 的含義與 CoGetClassObject

對應的參數一緻,(CoCreateInstance 的 iid 和 ppv 參數與 CoGetClassObject

不同,一個是表示對象的接口資訊,一個是表示類廠的接口資訊)。參數 pUnknownOuter 與類廠接口的 CreateInstance

中對應的參數一緻,主要用于對象被聚合的情況。CoCreateInstance 函數把通過類廠建立對象的過程封裝起來,客戶程式僅僅要指定對象類的 CLSID

和待輸出的接口指針及接口 ID,客戶程式能夠不與類廠打交道。CoCreateInstance 能夠用以下的代碼實作:

(savetime 注:以下代碼中 ppv 指針的應用,好像應該是 void **)

DWORD dwClsContext, const IID&amp; iid, void *ppv)

IClassFactory *pCF;

HRESULT hr;

hr = CoGetClassObject(clsid,

dwClsContext, NULL, IID_IClassFactory,

(void *) pCF);

(FAILED(hr)) return hr;

hr = pCF-&gt;CreateInstance(pUnknownOuter, iid,

(void *)ppv);

pFC-&gt;Release();

return hr;

從這段代碼我們能夠看出,CoCreateInstance 函數首先利用 CoGetClassObject

函數建立類廠對象,然後用得到的類廠對象的接口指針建立真正的 COM 對象,最後把類廠對象釋放掉并傳回,這樣就把類廠屏蔽起來。

可是,用 CoCreateInstance 并不能建立遠端機器上的對象,由于在調用 CoGetClassObject

時,把第三個用于指定server資訊的參數設定為 NULL。假設要建立遠端對象,能夠使用 CoCreateInstance 的擴充函數

CoCreateInstanceEx:

HRESULT CoCreateInstanceEx(const CLSID&amp; clsid, IUnknown

*pUnknownOuter,

DWORD dwClsContext, COSERVERINFO *pServerInfo, DWORD dwCount,

MULTI_QI *rgMultiQI);

前三個參數與 CoCreateInstance 一樣,pServerInfo 與 CoGetClassOjbect

的參數一樣,用于指定server資訊,最後兩個參數 dwCount 和 rgMultiQI

指定了一個結構數組,能夠用于儲存多個對象接口指針,其目的在于一次獲得多個接口指針,以便降低客戶程式與元件程式之間的頻繁互動,這對于網絡環境下的遠端對象是非常有意義的。

COM 庫的初始化

調用 COM 庫的函數之前,為了使函數有效,必須調用 COM 庫的初始化函數:

HRESULT CoInitialize(IMalloc *pMalloc);

pMalloc 用于指定一個記憶體配置設定器,可由應用程式指定記憶體配置設定原則。普通情況下,我們直接把參數設為 NULL,則 COM

庫将使用預設提供的記憶體配置設定器。

傳回值:S_OK 表示初始化成功

S_FALSE 表示初始化成功,但這次調用不是本程序中首次調用初始化函數

S_UNEXPECTED 表示初始化過程中發生了錯誤,應用程式不能使用 COM 庫

通常,一個程序對 COM 庫僅僅進行一次初始化,并且,在同一個子產品單元中對 COM 庫進行多次初始化并沒有意義。唯一不須要初始化 COM 庫的函數是擷取

COM 庫版本号的函數:

DWORD CoBuildVersion();

傳回值:高 16 位 主版本号号

低 16 位 次版本号号

COM 程式在用完 COM 庫服務之後,一般是在程式退出之前,一定要調用終止 COM 庫服務函數,以便釋放 COM 庫所維護的資源:

void CoUninitialize(void);

注意:凡是調用 CoInitialize 函數傳回 S_OK 的程序或程式子產品一定要有對應的 CoUninitialize 函數調用,以保證 COM

庫有效地利用資源。

(? 假設在一個子產品中調用 CoInitialize 傳回 S_OK,那麼它調用 CoUnitialize 函數後,其它也在使用 COM

庫的子產品是否會出錯誤?還是 COM 庫會自己主動檢查有哪些子產品在使用?)

COM 庫的記憶體管理

由于 COM 元件程式和客戶程式是通過二進制級标準建立連接配接的,是以在 COM 應用程式中凡是涉及客戶、COM

庫群組件三者之間記憶體互動(配置設定和釋放不在同一個子產品中)的操作必須使用一緻的記憶體管理器。COM 提供的記憶體管理标準,實際上是一個 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; // 傳回配置設定的記憶體大小

int DidAlloc(void *pv) = 0; //

确定記憶體指針是否由該記憶體管理器配置設定

void HeapMinimize() = 0; // 使堆記憶體盡可能降低,把沒用到的記憶體還給

// 作業系統,用于性能優化

獲得 IMalloc 接口指針:

HRESULT CoGetMalloc(DWORD dwMemContext, IMalloc **ppMalloc);

CoGetMalloc 函數的第一個參數 dwMemContext 用于指定記憶體管理器的類型。COM

庫中包括兩種記憶體管理器,一種就是在初始化時指定的記憶體管理器或者其内部預設的管理器,也稱為作業管理器(task

allocator),這樣的管理器在本程序内有效,要擷取該管理器,在 dwMemContext 參數中指定為

MEMCTX_TASK;還有一種是跨程序的共享配置設定器,由 OLE 系統提供,要擷取這樣的管理器,dwMemContext 參數中指定為

MEMCTX_SHARED,使用共享管理器的便利是,能夠在一個程序内配置設定記憶體并傳給第二個程序,在第二個程序内使用此記憶體甚至釋放掉此記憶體。

僅僅要函數的傳回值為 S_OK,則 ppMalloc 就指向了 COM 庫的記憶體管理器接口指針,能夠使用它進行記憶體操作,使用完畢後,應該調用

Release 成員函數釋放控制權。

COM 庫封裝了三個 API 函數,可用于記憶體配置設定和釋放:

void * CoTaskMemAlloc(ULONG cb);

void CoTaskFree(void *pv);

void

CoTaskMemRealloc(void *pv, ULONG cb);

這三個函數配置設定對應于 IMalloc 的三個成員函數:Alloc、Realloc 和 Free。

例:COM 程式怎樣從 CLSID 值找到對應的 ProgID 值:

WCHAR *pwProgID;

char pszProgID[128];

hResult =

::ProgIDFromCLSID(CLSID_Dictionary, &amp;pwProgID);

if (hResult != S_OK)

wcstombs(pszProgID, pwProgID, 128);

CoTaskMemFree(pwProgID); // 注意:必須釋放記憶體

在調用 COM 函數 ProgIDFromCLSID 傳回之後,由于 COM 庫為輸出變量 pwProgID 配置設定了記憶體空間,是以應用程式在用完

pwProgID 變量之後,一定要調用 CoTaskMemFree 函數釋放記憶體。該樣例說明了在 COM 庫中配置設定記憶體,而在調用程式中釋放記憶體的一種情況。COM

庫中其它一些函數也有相似的特性,尤其是一些包括不定長度輸出參數的函數。

元件程式的裝載和解除安裝

程序内元件的裝載:

客戶程式調用COM 庫的 CoCreateInstance 或 CoGetClassObject 函數建立 COM 對象,在

CoGetClassObject 函數中,COM 庫依據系統注冊表中的資訊,找到類辨別符 CLSID 對應的元件程式(DLL 檔案)的全路徑,然後調用

LoadLibrary(實際上是 CoLoadLibrary)函數,并調用元件程式的 DllGetClassObject

引出函數。DllGetClassObject 函數建立對應的類廠對象,并傳回類廠對象的 IClassFactory 接口。至此 CoGetClassObject

函數的任務完畢,然後客戶程式或者 CoCreateInstance 函數繼續調用類廠對象的 CreateInstance 成員函數,由它負責 COM

對象的建立工作。

CoCreateInstance

|-CoGetClassObject

|-Get CLSID -&gt; DLLfile

path

|-CoLoadLibrary

|-DLLfile.DllGetClassObject

|-return

IClassFactory

|-IClassFactory.CreateInstnace

程序外元件的裝載:

在 COM 庫的 CoGetClassObject 函數中,當它發現元件程式是 EXE 檔案(由注冊表元件對象資訊中的 LocalServer 或

LocalServer32 值指定)時,COM

庫建立一個程序啟動元件程式,并帶上“/Embedding”指令行參數,然後等待元件程式;而元件程式在啟動後,當它檢查到“/Embedding”指令行參數後,就會建立類廠對象,然後調用

CoRegisterClassObject 函數把類廠對象注冊到 COM 中。當 COM 庫檢查到元件對象的類廠之後,CoGetClassObject

函數就把類廠對象傳回。由于類廠與客戶程式運作在不同的程序中,是以客戶程式得到的是類廠的代理對象。一旦客戶程式或 COM

庫得到了類廠對象,它就能夠完畢元件對象的建立工作。

程序内對象和程序外對象的不同建立過程僅僅影響了 CoGetClassObject 函數的實作過程,對于客戶程式來說是全然透明的。

CoGetClassObject

|-LocalServer/LocalServer32

|-Execute EXE

/Embedding

|-Create class factory

|-CoRegisterClassObject ( class

factory )

|-return class factory (proxy)

程序内元件的解除安裝:

僅僅有當元件程式滿足了兩個條件時,它才幹被解除安裝,這兩個條件是:元件中對象數為 0,類廠的鎖計數為 0。滿足這兩個條件時,DllCanUnloadNow

引出函數傳回 TRUE。COM 提供了一個函數 CoFreeUnusedLibraries,它會檢測目前程序中的全部元件程式,當發現某個元件程式的

DllCanUnloadNow 函數傳回 TRUE 時,就調用 FreeLibrary 函數(實際上是 CoFreeLibrary

函數)把該元件從程式從記憶體中卸出。

該由誰來調用 CoFreeUnusedLibraries

函數呢?由于在元件程式運作過程中,它不可能把自己從記憶體中卸出,是以這個任務應該由客戶來完畢。客戶程式随時都能夠調用 CoFreeUnusedLibraries

函數完畢卸出工作,但通常的做法是,在程式的空暇處理過程中調用 CoFreeUnusedLibraries 函數,這樣做既能夠避免程式中處處考慮對

CoFreeUnusedLibraries 函數的調用,又能夠使不再使用的元件程式得到及時清除,提高資源的使用率,COM 規範也推薦這樣的做法。

程序外元件的解除安裝:

程序外元件的解除安裝比較簡單,由于元件程式運作在單獨的程序中,一旦其退出的條件滿足,它僅僅要從程序的主要函數傳回就可以。在 Windows

系統中,程序的主要函數為 WinMain。

前面曾經說過,在元件程式啟動運作時,它調用 CoRegisterClassObject 函數,把類廠對象注冊到 COM

中,注冊之後,類廠對象的引用計數始終大于 0,是以單憑類廠對象的引用計數無法控制程序的生存期,這也是引入類廠對象的加鎖和減鎖操作的原因。程序外元件的載條件與

DllCanUnloadNow 中的推斷相似,也須要推斷 COM 對象是否還存在、以及推斷是否鎖計數器為

0,僅僅有當條件滿足了,程序的主函數才幹夠退出。

從原則上講,程序外元件程式的解除安裝就是這麼簡單,但實際上情況可能複雜一些,由于有些元件程式在運作過程中能夠建立自己的對象,或者包括使用者界面的程式在運作過程中,使用者手工關閉了程序,那麼程序對這些動作的處理要複雜一些。比如,元件程式在運作過程中,使用者又打開了一個檔案并進行操作,那麼即使原先建立的對象被釋放了,并且鎖計數器也為

0,程序也不能退出,它必須繼續為使用者服務,就像是使用者打開的程序一樣。對這樣的程式,能夠增加一個“使用者控制”标記 flag,假設 flag 為

FALSE,則能夠按簡單的方法直接退出程式就可以;假設 flag 為 TRUE,則表明使用者參與了控制,元件程序不能立即退出,但應該調用

CoRevokeClassObject 函數以便與 CoRegisterClassObject 調用相響呼應,把程序留給使用者繼續進行。

假設元件程式在運作過程中,使用者要關閉程序,而此時并不滿足程序退出條件,那麼程序能夠採取兩種辦法:第一種方法,把應用隐藏起來,并把 flag 标記設定為

FALSE,然後元件程式繼續運作直到解除安裝條件滿足為止;還有一種辦法是,調用 CoDisconnectObject

函數,強迫脫離對象與客戶之間的關系,并強行終止程序,這樣的方法比較粗暴,不提倡採用,但不得已時能夠也使用,以保證系統完畢一些高優先級的操作。

COM 庫經常使用函數

初始化函數 CoBuildVersion 獲得 COM 庫的版本号号

CoInitialize COM 庫初始化

CoUninitialize COM 庫功能服務終止

CoFreeUnusedLibraries 釋放程序中全部不再使用的元件程式

GUID 相關函數 IsEqualGUID 推斷兩個 GUID 是否相等

IsEqualIID 推斷兩個 IID 是否相等

IsEqualCLSID 推斷兩個 CLSID 是否相等 (*為什麼要3個函數)

CLSIDFromProgID 字元串元件辨別轉換為 CLSID 形式

StringFromCLSID CLSID 形式辨別轉化為字元串形式

IIDFromString 字元串轉換為 IID 形式

StringFromIID IID 形式轉換為字元串

StringFromGUID2 GUID 形式轉換為字元串(*為什麼有 2)

對象建立函數 CoGetClassObject 擷取類廠對象

CoCreateInstance 建立 COM 對象

CoCreateInstanceEx 建立 COM 對象,可指定多個接口或遠端對象

CoRegisterClassObject 登記一個對象,使其它應用程式能夠連接配接到它

CoRevokeClassObject 取消對象的登記

CoDisconnectObject 斷開其它應用與對象的連接配接

記憶體管理函數 CoTaskMemAlloc 記憶體配置設定函數

CoTaskMemRealloc 記憶體又一次配置設定函數

CoTaskMemFree 記憶體釋放函數

CoGetMalloc 擷取 COM 庫記憶體管理器接口

HRESULT 類型

大多數 COM 函數以及一些接口成員函數的傳回值類型均為 HRESULT 類型。HRESULT

類型的傳回值反映了函數中的一些情況,其類型定義規範例如以下:

31 30 29 28 16 15 0

|-----|--|------------------------|-----------------------------------|

類别碼 (30-31) 反映函數調用結果:

00 調用成功

01 包括一些資訊

10 警告

11 錯誤

自己定義标記(29) 反映結果是否為自己定義辨別,1 為是,0 則不是;

操作碼 (16-28) 辨別結果操作來源,在 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

FACILITY_INTERNET 12

#define FACILITY_ITF 4

FACILITY_DISPATCH 2

#define FACILITY_CERT 11

操作結果碼(0-15) 反映操作的狀态,WinError.h 定義了 Win32 函數全部可能傳回結果。

以下是一些經經常使用到的傳回值和宏定義:

S_OK 函數運作成功,其值為 0 (注意,其值與 TRUE 相反)

S_FALSE 函數運作成功,其值為 1

S_FAIL 函數運作失敗,失敗原因不确定

E_OUTOFMEMORY 函數運作失敗,失敗原由于記憶體配置設定不成功

E_NOTIMPL 函數運作失敗,成員函數沒有被實作

E_NOTINTERFACE 函數運作失敗,元件沒有實作指定的接口

不能簡單地把傳回值與 S_OK 和 S_FALSE 比較,而要用 SECCEEDED 和 FAILED 宏進行推斷。

⊙ 第四章 COM 特性

可重用性:包容和聚合

包容模型:

元件對象在接口的實作代碼中運作自身建立的還有一個元件對象的接口函數(客戶/server模型)。這個對象同一時候實作了兩個(或很多其它)接口的代碼。

聚合模型:

元件對象在接口的查詢代碼中把接口傳遞給自已建立的還有一個對象的接口查詢函數,而不實作該接口的代碼。還有一個對象必須實作聚合模型(也就是說,它知道自己正在被還有一個元件對象聚合),以便

QueryInterface 函數能夠正常運作。

在元件對象被聚合的情況下,當客戶請求它所不支援的接口或者請求 IUnknown

接口時,它必須把控制交給外部對象,由外部對象決定客戶程式的請求結果。

聚合模型展現了元件軟體真正意義上的重用。

聚合模型實作的關鍵在 CoCreateInstance 函數和 IClassFactory 接口:

// class

IClassFactory : public IUnknown

當中 pUnknownOuter 參數用于指定元件對象是否被聚合。假設 pUnknownOuter 參數為

NULL,說明元件對象正常使用,否則說明被聚合使用,pUnknownOuter 是外部元件對象的接口指針。

聚合模型下的被聚合對象的引用計數成員函數也要進行特别處理。在未被聚合的情況下,能夠使用一般的引用計數方法。在被聚合時,由客戶調用

AddRef/Release 函數時,必須轉向外部元件對象的 AddRef/Release

方法。這時,外部元件對象要控制被聚合的對象必須採用其它的引用計數接口。

程序透明性 (待學)

安全性(待學)

多線程特性(待學)

⊙ 第五章 用 Visual C++ 開發 COM 應用

Win32 SDK 提供的一些頭檔案的說明

Unknwn.h 标準接口 IUnknown 和 IClassFacatory 的 IID 及接口成員函數的定義

Wtypes.h 包括 COM 使用的資料結構的說明

Objidl.h 全部标準接口的定義,就可以用于 C 語言風格的定義,也可用于 C++ 語言 Comdef.h 全部标準接口以及 COM 和 OLE

内部對象的 CLSID ObjBase.h 全部的 COM API 函數的說明 Ole2.h 全部經過封裝的 OLE 輔助函數

與 COM 接口有關的一些宏

DECLARE_INTERFACE(iface)

聲明接口 iface,它不從其它的接口派生

DECLARE_INTERFACE_(iface, baseiface)

聲明接口 iface,它從接口 baseiface 派生

STDMETHOD(method)

聲明接口成員函數 method,函數傳回類型為 HRESULT

STDMETHOD_(type, method) 聲明接口成員函數 method,函數傳回類型為 type

⊙ 結 束