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