在使用DELPHI開發(fā)軟件的過(guò)程中,我們就像草原上一群快樂牛羊,無(wú)憂無(wú)慮地享受著Object Pascal語(yǔ)言為我們帶來(lái)的陽(yáng)光和各種VCL控件提供的豐富的水草。抬頭望望無(wú)邊無(wú)際蔚藍(lán)的天空,低頭品嘗大地上茂密的青草,誰(shuí)會(huì)去想宇宙有多大,比分子和原子更小的東西是什么?那是哲學(xué)家的事。而哲學(xué)家此時(shí)正坐在高高的山頂上,仰望宇宙星云變換,凝視地上小蟲的爬行,驀然回頭,對(duì)我們這群吃草的牛羊點(diǎn)頭微笑。隨手扯起一根小草,輕輕地含在嘴里,閉上眼睛細(xì)細(xì)品嘗,不知道這根青草在哲學(xué)家的嘴里是什么味道?只是,他的臉上一直帶著滿意的微笑。
認(rèn)識(shí)和了解DELPHI微觀的原子世界,可以使我們徹底理解DELPHI的宏觀應(yīng)用程序結(jié)構(gòu),從而在更廣闊的思想空間中開發(fā)我們的軟件。這就好像,牛頓發(fā)現(xiàn)了宏觀物體的運(yùn)動(dòng),卻因?yàn)楦悴磺逦矬w為什么會(huì)這樣運(yùn)動(dòng)而苦惱,相反,愛因斯坦卻在基本粒子規(guī)律和宏觀物體運(yùn)動(dòng)之間體驗(yàn)著相對(duì)論的快樂生活!
第一節(jié) TObject原子
TObject是什么?
是Object Pascal語(yǔ)言體系結(jié)構(gòu)的基本核心,也是各種VCL控件的起源。我們可以認(rèn)為,TObject是構(gòu)成DELPHI應(yīng)用程序的原子之一,當(dāng)然,他們又是由基本Pascal語(yǔ)法元素等更細(xì)微的粒子構(gòu)成。
說(shuō)TObject是DELPHI程序的原子,是因?yàn)門Object是DELPHI編譯器內(nèi)部支持的。所有的對(duì)象類都是從TObject派生的,即使你并未指定TObject為祖先類。TObject被定義在System單元,它是系統(tǒng)的一部分。在System.pas單元的開頭,有這樣的注釋文本:
{ PRedefined constants, types, procedures, }
{ and functions (such as True, Integer, or }
{ Writeln) do not have actual declarations.}
{ Instead they are built into the compiler }
{ and are treated as if they were declared }
{ at the beginning of the System unit. }
它的意思說(shuō),這一單元包含預(yù)定義的常量、類型、過(guò)程和函數(shù)(諸如:Ture、Integer或Writeln),它們并沒有實(shí)際的聲明,而是編譯器內(nèi)置的,并在編譯的開始就被認(rèn)為是已經(jīng)聲明的定義。你可以將Classes.pas或Windows.pas等其他源程序文件加入你的項(xiàng)目文件中進(jìn)行編譯和調(diào)試其源代碼,但你絕對(duì)無(wú)法將System.pas源程序文件加入到你的項(xiàng)目文件中進(jìn)行編譯!DELPHI將報(bào)告重復(fù)定義System的編譯錯(cuò)誤!
因此,TObject是編譯器內(nèi)部提供的定義,對(duì)于我們使用DELPHI開發(fā)程序的人來(lái)說(shuō),TObject是原子性的東西。
TObject在System單元中的定義是這樣的:
TObject = class
constructor Create;
procedure Free;
class function InitInstance(Instance: Pointer): TObject;
procedure CleanupInstance;
function ClassType: TClass;
class function ClassName: ShortString;
class function ClassNameIs(const Name: string): Boolean;
class function ClassParent: TClass;
class function ClassInfo: Pointer;
class function InstanceSize: Longint;
class function InheritsFrom(AClass: TClass): Boolean;
class function MethodAddress(const Name: ShortString): Pointer;
class function MethodName(Address: Pointer): ShortString;
function FieldAddress(const Name: ShortString): Pointer;
function GetInterface(const IID: TGUID; out Obj): Boolean;
class function GetInterfaceEntry(const IID: TGUID): PInterfaceEntry;
class function GetInterfaceTable: PInterfaceTable;
function SafeCallException(ExceptObject: TObject;
ExceptAddr: Pointer): HResult; virtual;
procedure AfterConstruction; virtual;
procedure BeforeDestruction; virtual;
procedure Dispatch(var Message); virtual;
procedure DefaultHandler(var Message); virtual;
class function NewInstance: TObject; virtual;
procedure FreeInstance; virtual;
destructor Destroy; virtual;
end;
下面,我們將逐步敲開TObject原子的大門,看看里面到底是什么結(jié)構(gòu)。
我們知道,TObject是所有對(duì)象的基本類,那么,一個(gè)對(duì)象到底是什么?
DELPHI中的任何對(duì)象都是一個(gè)指針,這個(gè)指針指明該對(duì)象在內(nèi)存中所占據(jù)的一塊空間!雖然,對(duì)象是一個(gè)指針,可是我們引用對(duì)象的成員時(shí)卻不用寫成這樣的代碼MyObject^.GetName,而只能寫成MyObject.GetName,這是Object Pascal語(yǔ)言擴(kuò)充的語(yǔ)法,是由編譯器支持的。使用C++ Builder的朋友就很清楚對(duì)象與指針的關(guān)系,因?yàn)樵贑++ Builder的對(duì)象都要定義為指針。對(duì)象指針指向的地方就是對(duì)象存儲(chǔ)數(shù)據(jù)的對(duì)象空間,我們來(lái)分析一下對(duì)象指針指向的內(nèi)存空間的數(shù)據(jù)結(jié)構(gòu)。
對(duì)象空間的頭4個(gè)字節(jié)是指向該對(duì)象類的虛方法地址表(VMT ?C Vritual Method Table)。接下來(lái)的空間就是存儲(chǔ)對(duì)象本身成員數(shù)據(jù)的空間,并按從該對(duì)象最原始祖先類的數(shù)據(jù)成員到該對(duì)象類的數(shù)據(jù)成員的總順序,和每一級(jí)類中數(shù)據(jù)成員的定義順序存儲(chǔ)。
類的虛方法地址表(VMT)保存從該類的原始祖先類派生到該類的所有類的虛方法的過(guò)程地址。類的虛方法,就是用保留字vritual聲明的方法,虛方法是實(shí)現(xiàn)對(duì)象多態(tài)性的基本機(jī)制。雖然,用保留字dynamic聲明的動(dòng)態(tài)方法也可實(shí)現(xiàn)對(duì)象的多態(tài)性,但這樣的方法不保存在虛方法地址表(VMT)中,它只是Object Pascal提供的另一種可節(jié)約類存儲(chǔ)空間的多態(tài)實(shí)現(xiàn)機(jī)制,但卻是以犧牲調(diào)用速度為代價(jià)的。
即使,我們自己并未定義任何類的虛方法,但該類的對(duì)象仍然存在指向虛方法地址表的指針,只是地址項(xiàng)的長(zhǎng)度為零。可是,在TObject中定義的那些虛方法,如Destroy、FreeInstance等等,又存儲(chǔ)在什么地方呢?原來(lái),他們的方法地址存儲(chǔ)在相對(duì)VMT指針負(fù)方向偏移的空間中。其實(shí),在VMT表的負(fù)方向偏移76個(gè)字節(jié)的數(shù)據(jù)空間是對(duì)象類的系統(tǒng)數(shù)據(jù)結(jié)構(gòu),這些數(shù)據(jù)結(jié)構(gòu)是與編譯器相關(guān)的,并且在將來(lái)的DELPHI版本中有可能被改變。
因此,你可以認(rèn)為,VMT是一個(gè)從負(fù)偏移地址空間開始的數(shù)據(jù)結(jié)構(gòu),負(fù)偏移數(shù)據(jù)區(qū)是VMT的系統(tǒng)數(shù)據(jù)區(qū),VMT的正偏移數(shù)據(jù)是用戶數(shù)據(jù)區(qū)(自定義的虛方法地址表)。TObject中定義的有關(guān)類信息或?qū)ο筮\(yùn)行時(shí)刻信息的函數(shù)和過(guò)程,一般都與VMT的系統(tǒng)數(shù)據(jù)有關(guān)。
一個(gè)VMT數(shù)據(jù)就代表一個(gè)類,其實(shí)VMT就是類!在Object Pascal中我們用TObject、TComponent等等標(biāo)識(shí)符表示類,它們?cè)贒ELPHI的內(nèi)部實(shí)現(xiàn)為各自的VMT數(shù)據(jù)。而用class of保留字定義的類的類型,實(shí)際就是指向相關(guān)VMT數(shù)據(jù)的指針。
對(duì)我們的應(yīng)用程序來(lái)說(shuō),VMT數(shù)據(jù)是靜態(tài)的數(shù)據(jù),當(dāng)編譯器編譯完成我們的應(yīng)用程序之后,這些數(shù)據(jù)信息已經(jīng)確定并已初始化。我們編寫的程序語(yǔ)句可訪問VMT相關(guān)的信息,獲得諸如對(duì)象的尺寸、類名或運(yùn)行時(shí)刻的屬性資料等等信息,或者調(diào)用虛方法或讀取方法的名稱與地址等等操作。
當(dāng)一個(gè)對(duì)象產(chǎn)生時(shí),系統(tǒng)會(huì)為該對(duì)象分配一塊內(nèi)存空間,并將該對(duì)象與相關(guān)的類聯(lián)系起來(lái),于是,在為對(duì)象分配的數(shù)據(jù)空間中的頭4個(gè)字節(jié),就成為指向類VMT數(shù)據(jù)的指針。
我們?cè)賮?lái)看看對(duì)象是怎樣誕生和滅亡的??粗胰龤q的兒子在草地上活蹦亂跳,正是由于親眼目睹過(guò)生命的誕生過(guò)程,我才能真真體會(huì)到生命的意義和偉大。也只有那些經(jīng)歷過(guò)死別的人,才會(huì)更加理解和珍惜生命。那么,就讓我們理解一下對(duì)象的產(chǎn)生和消亡的過(guò)程吧!
我們都知道,用下面的語(yǔ)句可以構(gòu)造一個(gè)最簡(jiǎn)單對(duì)象:
AnObject := TObject.Create;
編譯器將其編譯實(shí)現(xiàn)為:
用TObject對(duì)應(yīng)的VMT為依據(jù),調(diào)用TObject的Create構(gòu)造函數(shù)。而在Create構(gòu)造函數(shù)調(diào)用了系統(tǒng)的ClassCreate過(guò)程,系統(tǒng)的ClassCreate過(guò)程又通過(guò)存儲(chǔ)在類VMT調(diào)用NewInstance虛方法。調(diào)用NewInstance方法的目的是要建立對(duì)象的實(shí)例空間,因?yàn)槲覀儧]有重載該方法,所以,它就是TObject類的NewInstance。TObjec類的NewInstance方法將根據(jù)編譯器在VMT表中初始化的對(duì)象實(shí)例尺寸(InstanceSize),調(diào)用GetMem過(guò)程為該對(duì)象分配內(nèi)存,然后調(diào)用InitInstance方法將分配的空間初始化。InitInstance方法首先將對(duì)象空間的頭4個(gè)字節(jié)初始化為指向?qū)ο箢悓?duì)應(yīng)VMT的指針,然后將其余的空間清零。建立對(duì)象實(shí)例之后,還調(diào)用了一個(gè)虛方法AfterConstruction。最后,將對(duì)象實(shí)例數(shù)據(jù)的地址指針保存到AnObject變量中,這樣,AnObject對(duì)象就誕生了。
同樣,用下面的語(yǔ)句可以消滅一個(gè)對(duì)象:
AnObject.Destroy;
TObject的析構(gòu)函數(shù)Destroy被聲明為虛方法,它也是系統(tǒng)固有的虛方法之一。Destory方法首先調(diào)用了BeforeDestruction虛方法,然后調(diào)用系統(tǒng)的ClassDestroy過(guò)程。ClassDestory過(guò)程又通過(guò)類VMT調(diào)用FreeInstance虛方法,由FreeInstance方法調(diào)用FreeMem過(guò)程釋放對(duì)象的內(nèi)存空間。就這樣,一個(gè)對(duì)象就在系統(tǒng)中消失。
對(duì)象的析構(gòu)過(guò)程比對(duì)象的構(gòu)造過(guò)程簡(jiǎn)單,就好像生命的誕生是一個(gè)漫長(zhǎng)的孕育過(guò)程,而死亡卻相對(duì)的短暫,這似乎是一種必然的規(guī)律。
在對(duì)象的構(gòu)造和析構(gòu)過(guò)程中,調(diào)用了NewInstance和FreeInstance兩個(gè)虛函數(shù),來(lái)創(chuàng)建和釋放對(duì)象實(shí)例的內(nèi)存空間。之所以將這兩個(gè)函數(shù)聲明為虛函數(shù),是為了能讓用戶在編寫需要用戶自己管理內(nèi)存的特殊對(duì)象類時(shí)(如在一些特殊的工業(yè)控制程序中),有擴(kuò)展的空間。
而將AfterConstruction和BeforeDestruction聲明為虛函數(shù),也是為了將來(lái)派生的類在產(chǎn)生對(duì)象之后,有機(jī)會(huì)讓新誕生的對(duì)象呼吸第一口新鮮空氣,而在對(duì)象消亡之前可以允許對(duì)象完成善后事宜,這都是合情合理的事。其實(shí),TForm對(duì)象和TDataModule對(duì)象的OnCreate事件和OnDestroy事件,就是在TForm和TDataModule重載的這兩個(gè)虛函數(shù)過(guò)程分別觸發(fā)的。
此外,TObjec還提供了一個(gè)Free方法,它不是虛方法,它是為了那些搞不清對(duì)象是否為空(nil)的情況下能安全釋放對(duì)象而專門提供的。其實(shí),搞不清對(duì)象是否為空,本身就有程序邏輯不清晰的問題。不過(guò),任何人都不是完美的,都可能犯錯(cuò),使用Free能避免偶然的錯(cuò)誤也是件好事。然而,編寫正確的程序不能一味依靠這樣的解決方法,還是應(yīng)該以保證程序的邏輯正確性為編程的第一目標(biāo)!
有興趣的朋友可以讀一讀System單元的原代碼,其中,大量的代碼是用匯編語(yǔ)言書寫的。細(xì)心的朋友可以發(fā)現(xiàn),TObject的構(gòu)造函數(shù)Create和析構(gòu)函數(shù)Destory竟然沒有寫任何代碼,其實(shí),在調(diào)試狀態(tài)下通過(guò)Debug的CPU窗口,可清楚地反映出Create和Destory的匯編代碼。因?yàn)椋喸霥ELPHI的大師門不想將過(guò)多復(fù)雜的東西提供給用戶,他們希望用戶在簡(jiǎn)單的概念上編寫應(yīng)用程序,將復(fù)雜的工作隱藏在系統(tǒng)的內(nèi)部由他們承擔(dān)。所以,在發(fā)布System.pas單元時(shí)特別將這兩個(gè)函數(shù)的代碼去掉,讓用戶認(rèn)為TObject是萬(wàn)物之源,用戶派生的類完全從虛無(wú)中開始,這本身并沒有錯(cuò)。雖然,閱讀DELPHI的這些最本質(zhì)的代碼需要少量的匯編語(yǔ)言知識(shí),但閱讀這樣的代碼,可以讓我們更深刻認(rèn)識(shí)DELPHI世界的起源和發(fā)展的基本規(guī)律。即使看不太懂,能起碼了解一些基本東西,對(duì)我們編寫DELPHI程序也是大有幫助。
第二節(jié) TClass原子
在System.pas單元中,TClass是這樣定義的:
TClass = class of TObject;
它的意思是說(shuō),TClass是TObject的類。因?yàn)門Object本身就是一個(gè)類,所以TClass就是所謂的類的類。
從概念上說(shuō),TClass是類的類型,即,類之類。但是,我們知道DELPHI的一個(gè)類,代表著一項(xiàng)VMT數(shù)據(jù)。因此,類之類可以認(rèn)為是為VMT數(shù)據(jù)項(xiàng)定義的類型,其實(shí),它就是一個(gè)指向VMT數(shù)據(jù)的指針類型!
在以前傳統(tǒng)的C++語(yǔ)言中,是不能定義類的類型的。對(duì)象一旦編譯就固定下來(lái),類的結(jié)構(gòu)信息已經(jīng)轉(zhuǎn)化為絕對(duì)的機(jī)器代碼,在內(nèi)存中將不存在完整的類信息。一些較高級(jí)的面向?qū)ο笳Z(yǔ)言才可支持對(duì)類信息的動(dòng)態(tài)訪問和調(diào)用,但往往需要一套復(fù)雜的內(nèi)部解釋機(jī)制和較多的系統(tǒng)資源。而DELPHI的Object Pascal語(yǔ)言吸收了一些高級(jí)面向?qū)ο笳Z(yǔ)言的優(yōu)秀特征,又保留可將程序直接編譯成機(jī)器代碼的傳統(tǒng)優(yōu)點(diǎn),比較完美地解決了高級(jí)功能與程序效率的問題。
正是由于DELPHI在應(yīng)用程序中保留了完整的類信息,才能提供諸如as和is等在運(yùn)行時(shí)刻轉(zhuǎn)換和判別類的高級(jí)面向?qū)ο蠊δ埽惖腣MT數(shù)據(jù)在其中起了關(guān)鍵性的核心作用。有興趣的朋友可以讀一讀System單元的AsClass和IsClass兩個(gè)匯編過(guò)程,他們是as和is操作符的實(shí)現(xiàn)代碼,以加深對(duì)類和VMT數(shù)據(jù)的理解。
......
后面的內(nèi)容還有對(duì)虛構(gòu)造函數(shù)的理解,Interface的實(shí)現(xiàn)機(jī)制和異常處理的實(shí)現(xiàn)機(jī)制,等等DLPHI的基本原理。希望五一之后能寫完,再貢獻(xiàn)給大家。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注