【轉】Delphi類和組件-Delphi的原子世界

[摘要]本文介紹Delphi類和組件的相關知識,包括:System、TObject、TClass、對象的消息處理機制等。程序員

「天蒼蒼,野茫茫,風吹草低見牛羊」在使用 DELPHI 開發應用軟件的過程當中,咱們就像草原上一羣快樂牛羊,無憂無慮地享受着 Object Pascal 語言爲咱們帶來的溫暖陽光和各類 VCL 控件提供的豐富水草。擡頭望望一望無際蔚藍的天空,低頭品嚐大地上茂密的青草,誰會去想天有多高?地有多大?陽光和水草又是從何而來?那是大師關心的事。而大師此時正坐在高高的山頂上,仰望宇宙星雲變換,凝視地上小蟲的爬行。驀然回頭,對咱們這羣吃草的牛羊點頭微笑。隨手扯起一根小草,輕輕地含在嘴裏,閉上眼睛細細品嚐。不知道這根青草在大師的嘴裏是什麼味道?只是,他的臉上一直帶着滿意的微笑。編程

第一節 System

不經意,偶然打開了 System.pas 的原程序文件,卻發現這裏竟是一個既熟悉又陌生的世界。在這裏有咱們熟知的東東,如:TObject、TClass、GUID、IUnknown、IDispatch ……但這些東西也是咱們所陌生的。在茫茫編程生涯中,咱們不斷地與這些東東打交道,都已經熟悉得宛如本身身體的一部分。但真想要去了解他們,也就人象想要了解自身同樣的茫然。安全

在 System.pas 單元的開頭,有這樣一段醒目的註釋文本:數據結構

 

1
2
3
4
5
6
{ 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. }

 

這段話的意思是說:「這一單元包含預約義的常量、類型、過程和函數(諸如:Ture、Integer 或 Writeln),它們並無實際的聲明,而是編譯器內置的,並在編譯的開始就被認爲是已經聲明的定義」。ide

System 單元不一樣於別的單元。你能夠將 Classes.pas 或 Windows.pas 等其餘 DELPHI 源程序文件加入你的項目文件中進行編譯,並在源代碼基礎上調試這些單元。但你絕對沒法將 System.pas 源程序文件加入到你的項目文件中編譯!DELPHI 將報告「重複定義了 System 單元」的編譯錯誤。函數

任何 DELPHI 的目標程序中,都自動包含 System 單元中的代碼,哪怕你的程序一句代碼也沒寫。看看下面的程序:工具

 

1
2
3
program Nothing;
begin
end .

 

這個程序用 DELPHI 6 編譯以後有 8K,用 DELPHI 5 編譯以後有 16K。而使用過 C 語言的朋友都知道,最簡單的 C 語言程序編譯以後是很是短小的,有的不到 1K。但 DELPHI 不是的。學習

這個什麼也不作的程序怎麼會有 8K 或 16K 的長度呢?這是由於其含有 System 單元的代碼。雖然這些代碼沒有 C 或 C++ 語言的啓動代碼那樣短小精悍,但裏面卻包含支撐整座 DELPHI 大廈的基石,是很牢靠的。開發工具

在 DELPHI6 中,Borland 爲了兼容其在 Linux 下的旗艦產品 Kylix,進一步精簡了 System 單元的基礎程序,將一部分與 Windows 系統相關的內容移到了別的單元。因此,上面最簡單的程序通過 DELPHI6 編譯生成的目標程序就比 DELPHI5 生成的小的多。其實,DELPHI 6 中的 System.pas 單元有一萬八千多行源程序,比 DELPHI 5 的多得多。這是由於在 DELPHI6 的那些支持 Kylix 的單元中,有些代碼同時寫了兩個版本,一個支持 Windows,一個支持 Linux,並在編譯宏命令的控制下生成各自操做系統的目標程序。Borland 完成這些程序改寫以後,就有可能將 DELPHI 編寫的程序移植到 Kylix 上。按照 Borland 提供的某些原則編寫的 DELPHI 程序能夠不用修改直接在 Kylix 上編譯,並在 LINUX 系統上運行。這對須要進行跨平臺開發的程序員來講無疑是個福音。目前,在真編譯的可視開發工具中,DELPHI 6 和 Kylix 恐怕是惟一能實現跨平臺編譯功能的開發工具。ui

蜻蜓點水,瀏覽一下 DELPHI 的源代碼是值得的。由於,DELPHI 的源代碼中蘊藏着豐富的養分,那都是大師們的傑做。若是,咱們開發的應用應用程序是一棵開花的樹,那麼,請在咱們擁有這份花滿枝丫的浪漫時,請不要忘了深埋在土壤裏的那一藤樹根。沒有樹根提供養分,就沒有爛漫的花枝。要知道,世界上任何一棵樹的樹根總比其樹冠更多,更茂盛,儘管人們看不到深埋在地下的樹根。

但瀏覽 DELPHI 的源程序也是很費精力的。雖然,大師們寫的程序大都風格一流,易於閱讀和理解,但代碼實在太多。閱讀 System.pas 單元就更不容易,其中的大量程序甚至是用匯編語言編寫的,這對有些朋友來講無異於天書。咱們無心逐一去解讀其中的奧祕,這可能會耗用咱們九九八十一個不眠之夜。但咱們總能學到一些編程風格,瞭解其中的一些內容,並能悟得一些道理,而這可能會讓咱們受益終身。

固然,我無心將 DELPHI 的源代碼神化爲聖典。由於,那也畢竟不是天書,也是人編寫的,也能抓到其中的幾隻臭蟲。但咱們本身又怎樣呢?

第二節 TObject

TObject 是什麼?

TObject 在 DELPHI 中就是與生俱來的東西,沒有什麼好問的。

不知道 TObject 是什麼,照樣能夠編寫出很好的 DELPHI 程序。咱們能夠當心苛護本身的 DELPHI 程序,「朝朝勤拂拭,莫讓惹塵埃」,咱們的程序也能照樣歡快地奔跑。世界上有不少的東西都是咱們不知道的,咱們同樣也生活得很好。

但世上總有些人就是喜歡去學習和探索那些不知道的東西,最終他們知道的東西總比別人多些,成爲了智者。我想,在編程中也是這樣,若是通過咱們不斷地學習和探索,將不知道的東西變成咱們知道的東西,咱們也會逐漸成爲編程中的智者。相信總有一天能進入「原本無一物,何處惹塵埃」的境界。

TObject 是 System 單元中定義的第一個類。因而可知它在 DELPHI 中的重要性。TObject 的定義是這樣的:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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 是 class 類型。

說到這裏,也許有人要問這須要特別注意嗎?

在此,我只是想提醒你們不要忘了,在 Object Pascal 語言中還有一種以 object 保留字定義的對象類型。這種數據板塊套上過程做爲方法的老古董,一樣實現了面向對象的各類特徵,只不過它並不是現代 DELPHI 大廈的奠定石。有點象是歷史文化遺產,屬於傳統文化系列。但瞭解歷史能夠更深入地理解如今並展望將來。如今,class 系列的對象類纔是 DELPHI 的基礎,它和對象的接口技術一塊兒,支撐起整個 DELPHI 大廈。咱們所講的對象幾乎都是 class 系列的。因此若是沒有特別指明,「對象」一詞都指 class 類型的對象。

咱們都知道,在 DELPHI 中 TObject 是全部 class 系列對象的基本類。也就是說,在 DELPHI 中,TObject 是萬物之源。無論你自定義的類是否指明瞭所繼承的父類,必定都是 TObject 的子孫,同樣具備 TObject 定義的全部特性。

那麼,一個對象究竟是什麼?

對象就是一個帶柄的南瓜。南瓜柄就是對象的指針,南瓜就是對象的數據體。確切地說,DELPHI 中的對象是一個指針,這個指針指向該對象在內存中所佔據的一塊空間。

雖然,對象是一個指針,但是咱們引用對象的成員時卻不能寫成這樣的代碼:

 

1
MyObject^.GetName;

 

而只能寫成:

 

1
MyObject . GetName;

 

這是 Object Pascal 語言擴充的語法,是由編譯器支持的。使用 C++ Builder 的朋友就很清楚對象與指針的關係,由於在 C++ Builder 的 VCL 對象都是經過指針引用的。

爲何說對象是一個指針呢?咱們能夠試着用 sizeof 函數獲取對象的大小,例如計算 sizeof(MyObject) 的值。結果是 4 字節,這就就是一個 32 位指針的大小,只是南瓜柄的大小。而對象的真正大小應該用 MyObject.InstanceSize 得到,這纔是南瓜應有的分量。廣義的說,咱們經常使用的「句柄」概念,英文叫 Handle,也是一個對象指針,由於它後面也連着一個別的什麼瓜。

既然 DELPHI 對象是指向一塊內存空間的指針,那麼,表明對象的這快內存空間又有怎樣的數據結構呢?就把南瓜切開來看看囉。

咱們將對象指針指向的內存空間稱爲對象空間。對象空間的頭 4 個字節是指向該對象直屬類的虛方法地址表(VMT – Vritual Method Table)。接下來的空間就是存儲對象自己成員數據的空間,並按從該對象最原始祖先類的數據成員到該對象具體類的數據成員的總順序,和每一級類中定義數據成員的排列順序存儲。

每個類都有對應的一張 VMT,類的 VMT 保存從該類的原始祖先類派生到該類的全部類的虛方法的過程地址。類的虛方法,就是用保留字 vritual 聲明的方法。虛方法是實現對象多態性的基本機制。雖然,用保留字 dynamic 聲明的動態方法也可實現對象的多態性。但這樣的方法不保存在 VMT 中。用保留字 dynamic 聲明的動態方法只是 Object Pascal 語言提供的另外一種可節約類存儲空間的多態實現機制,但倒是以犧牲調用速度爲代價的。

即便,咱們本身並未定義任何類的虛方法,但該類的對象仍然存在指向虛方法地址表的指針,只是地址項的長度爲零。但是,在 TObject 中定義的那些虛方法,如 Destroy、FreeInstance 等等,又存儲在什麼地方呢?原來,他們的方法地址存儲在相對 VMT 指針負方向偏移的空間中。在 VMT 的負方向偏移有 76 個字節的數據信息,它們是對象類的基本數據結構。而 VMT 是存儲咱們本身爲類定義的虛方法地址的地方,它只是類數據結的構擴展部分。VMT 前的 76 個字節的數據結構是 DELPHI 內定的,與編譯器相關的,而且在未來的 DELPHI 版本中有可能被改變。

下面的對象和類的結構草圖展現了對象和類之間的一些關係。

TObject 中定義的有關類信息或對象運行時刻信息的函數和過程,通常都與類的數據結構相關。

在 DELPHI 中咱們用 TObject、TComponent 等等標識符表示類,它們在 DELPHI 的內部實現爲各自的 VMT 數據。而用 class of 保留字定義的類的類型,實際就是指向相關 VMT 數據的指針。

對咱們的應用程序來講,類的數據是靜態的數據。當編譯器編譯完成咱們的應用程序以後,這些數據信息已經肯定並已初始化。咱們編寫的程序語句可訪問類數據中的相關信息,得到諸如對象的尺寸、類名或運行時刻的屬性資料等等信息,或者調用虛方法以及讀取方法的名稱與地址等等操做。

當一個對象產生時,系統會爲該對象分配一塊內存空間,並將該對象與相關的類聯繫起來。因而,在爲對象分配的數據空間中的頭 4 個字節,就成爲指向類 VMT 數據的指針。

咱們再來看看對象是怎樣誕生和滅亡的。咱們都知道,用下面的語句能夠構造一個最簡單對象:

 

1
AnObject := TObject . Create;

 

編譯器將其編譯實現爲,用 TObject 對應的類數據信息爲依據,調用 TObject 的 Create 構造函數。而 TObject 的 Create 構造函數調用了系統的 ClassCreate 過程。系統的 ClassCreate 過程又經過調用 TObject 類的虛方法 NewInstance。調用 TObject 的 NewInstance 方法的目的是要創建對象的實例空間。TObjec 類的 NewInstance 方法將根據編譯器在類信息數據中初始化的對象實例尺寸(InstanceSize),調用 GetMem 過程爲該對象分配內存。而後調用 TObject 類 InitInstance 方法將分配的空間初始化。InitInstance 方法首先將對象空間的頭 4 個字節初始化爲指向對象類的 VMT 的指針,而後將其他的空間清零。創建對象實例最後,還調用了一個虛方法 AfterConstruction。最後,將對象實例數據的地址指針保存到 AnObject 變量中,這樣,AnObject 對象就誕生了。

一樣,用下面的語句能夠消滅一個對象:

 

1
AnObject . Destroy;

 

TObject 的析構函數 Destroy 被聲明爲虛方法,這可讓某些有個性的對象選擇本身的死亡方法。Destory 方法首先調用了 BeforeDestruction 虛方法,而後調用系統的 ClassDestroy 過程。ClassDestory 過程又經過調用對象的 FreeInstance 虛方法。由 FreeInstance 方法調用 FreeMem 過程釋放對象的內存空間。就這樣,一個對象就在系統中消失。

對象的析構過程比對象的構造過程簡單,就好像生命的誕生是一個漫長的孕育過程,而死亡卻相對的短暫,這彷佛是一種必然的規律。

在對象的構造和析構過程當中,調用了 NewInstance 和 FreeInstance 兩個虛函數,來建立和釋放對象實例的內存空間。之因此將這兩個函數聲明爲虛函數,是爲了能讓用戶在編寫須要用戶本身管理內存的特殊對象類時(如在一些特殊的工業控制程序中),有擴展的空間。

而將 AfterConstruction 和 BeforeDestruction 聲明爲虛函數,也是爲了未來派生的類在產生對象以後,有機會讓新誕生的對象呼吸第一口新鮮空氣,而在對象消亡以前能夠容許對象交待最後的遺言,這都是合情合理的事。例如,咱們熟悉的 TForm 對象和 TdataModule 對象的 OnCreate 事件和 OnDestroy 事件,就是分別在這兩個重載的虛函數中觸發的。

此外,TObjec 還提供了一個 Free 方法。它不是虛方法,它是爲了在搞不清對象指針是否爲空(nil)的狀況下,也能安全釋放對象而專門提供的。固然,搞不清對象指針是不是否爲空,自己就有程序邏輯不清晰的問題。不過,任何人都不是完美的,均可能犯錯,使用 Free 能避免偶然的錯誤也是件好事。然而,編寫正確的程序不能一味依靠這樣的解決方法,仍是應該以保證程序的邏輯正確性爲編程的第一目標。

有興趣的朋友能夠讀一讀 System 單元的原代碼,其中,大量的代碼是用匯編語言書寫的。細心的朋友能夠發現,TObject 的構造函數 Create 和析構函數 Destory 居然沒有寫任何代碼。其實,在調試狀態下經過 Debug 的 CPU 窗口,可清楚地反映出 Create 和 Destory 的彙編代碼。我想,多是由於締造 DELPHI 的大師門不想將過多複雜的東西提供給用戶。他們但願用戶在簡單的概念上編寫應用程序,將複雜的工做隱藏在系統的內部由他們來承擔。因此,在編寫 System.pas 單元時特別將這兩個函數的代碼去掉,讓用戶認爲 TObject 是萬物之源,用戶派生的類徹底從虛無中開始,這自己並無錯。

第三節 TClass

在 System.pas 單元中,TClass 是這樣定義的:

 

1
TClass = class of TObject;

 

它的意思是說,TClass 是 TObject 的類。由於 TObject 自己就是一個類,因此 TClass 就是所謂的類的類。

從概念上說,TClass 是類的類型,即,類之類。可是,咱們知道 DELPHI 的一個類,表明着一項 VMT 數據。所以,類之類能夠認爲是爲 VMT 數據項定義的類型,其實,它就是一個指向 VMT 數據的指針類型!

在之前傳統的 C++ 語言中,是不能定義類的類型的。對象一旦編譯就固定下來,類的結構信息已經轉化爲絕對的機器代碼,在內存中將不存在完整的類信息。一些較高級的面嚮對象語言纔可支持對類信息的動態訪問和調用,但每每須要一套複雜的內部解釋機制和較多的系統資源。而 DELPHI 的 Object Pascal 語言吸取了一些高級面嚮對象語言的優秀特徵,又保留可將程序直接編譯成機器代碼的傳統優勢,比較完美地解決了高級功能與程序效率的問題。

正是因爲 DELPHI 在應用程序中保留了完整的類信息,才能提供諸如 as 和 is 等在運行時刻轉換和判別的高級面向對象功能,而類的 VMT 數據在其中起了關鍵性的核心做用。有興趣的朋友能夠讀一讀 System 單元的 AsClass 和 IsClass 兩個彙編過程,他們是 as 和 is 操做符的實現代碼,這樣能夠加深對類和 VMT 數據的理解。

有了類的類型,就能夠將類做爲變量來使用。能夠將類的變量理解爲一種特殊的對象,你能夠象訪問對象那樣訪問類變量的方法。例如:咱們來看看下面的程序片斷:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type
   TSampleClass = class of TSampleObject;
 
   TSampleObject = class ( TObject )
   public
     constructor Create;
     destructor Destroy; override;
     class function GetSampleObjectCount: Integer ;
     procedure GetObjectIndex: Integer ;
   end ;
 
var
   aSampleClass : TSampleClass;
   aClass : TClass;

 

在這段代碼中,咱們定義了一個類 TSampleObject 及其相關的類類型 TSampleClass,還包括兩個類變量 aSampleClass 和 aClass。此外,咱們還爲 TSampleObject 類定義了構造函數、析構函數、一個類方法 GetSampleObjectCount 和一個對象方法 GetObjectIndex。

首先,咱們來理解一下類變量 aSampleClass 和 aClass 的含義。

顯然,你能夠將 TSampleObject 和 TObject 看成常量值,並可將它們賦值給 aClass 變量,就好象將 123 常量值賦值給整數變量 i 同樣。因此,類類型、類和類變量的關係就是類型、常量和變量的關係,只不過是在類的這個層次上而不是對象層次上的關係。固然,直接將 TObject 賦值給 aSampleClass 是不合法的,由於 aSampleClass 是 TObject 派生類 TSampleObject 的類變量,而 TObject 並不包含與 TSampleClass 類型兼容的全部定義。相反,將 TSampleObject 賦值給 aClass 變量倒是合法的,由於 TSampleObject 是 TObject 的派生類,是和 TClass 類型兼容的。這與對象變量的賦值和類型匹配關係徹底類似。

而後,咱們再來看看什麼是類方法。

所謂類方法,就是指在類的層次上調用的方法,如上面所定義的 GetSampleObjectCount 方法,它是用保留字 class 聲明的方法。類方法是不一樣於在對象層次上調用的對象方法的,對象方法已經爲咱們所熟悉,而類方法老是在訪問和控制全部類對象的共同特性和集中管理對象這一個層次上使用的。

在 TObject 的定義中,咱們能夠發現大量的類方法,如 ClassName、ClassInfo 和 NewInstance 等等。其中,NewInstance 還被定義爲 virtual 的,即虛的類方法。這意味做你能夠在派生的子類中從新編寫 NewInstance 的實現方法,以便用特殊的方式構造該類的對象實例。

在類方法中你也可以使用 self 這一標識符,不過其所表明的含義與對象方法中的 self 是不一樣的。類方法中的 self 表示的是自身的類,即指向 VMT 的指針,而對象方法中的 self 表示的是對象自己,即指向對象數據空間的指針。雖然,類方法只能在類層次上使用,但你仍可經過一個對象去調用類方法。例如,能夠經過語句 aObject.ClassName 調用對象 TObject 的類方法 ClassName,由於對象指針所指向的對象數據空間中的頭 4 個字節又是指向類 VMT 的指針。相反,你不可能在類層次上調用對象方法,象 TObject.Free 的語句必定是非法的。

值得注意的是,構造函數是類方法,而析構函數是對象方法!

什麼?構造函數是類方法,析構函數是對象方法!有沒有搞錯?

你看看,當你建立對象時分明使用的是相似於下面的語句:

 

1
aObject := TObject . Create;

 

分明是調用類 TObject 的 Create 方法。而刪除對象時卻用的下面的語句:

 

1
aObject.Destroy;

 

難道不是嗎?TObject 是類,而 aObject 是對象。

緣由很簡單,在構造對象以前,對象還不存在,只存在類,建立對象只能用類方法。相反,刪除對象必定是刪除已經存在的對象,是對象被釋放,而不是類被釋放。

最後,順便討論一下虛構造函數的問題。

在傳統的 C++ 語言中,能夠實現虛析構函數,但實現虛構造函數倒是一個難題。由於,在傳統的 C++ 語言中,沒有類的類型。全局對象的實例是在編譯時就存在於全局數據空間中,函數的局部對象也是編譯時就在堆棧空間中映射的實例。即便是動態建立的對象,也是用 new 操做符按固定的類結構在堆空間中分配的實例,而構造函數只是一個對已產生的對象實例進行初始化的對象方法而已。傳統 C++ 語言沒有真正的類方法,即便能夠定義所謂靜態的基於類的方法,其最終也被實現爲一種特殊的全局函數。更不用說虛擬的類方法,虛方法只能針對具體的對象實例有效。所以,傳統的 C++ 語言認爲,在具體的對象實例產生以前,卻要根據即將產生的對象構造對象自己,這是不可能的。的確不可能,由於這會在邏輯上產生自相矛盾的悖論!

然而,正是因爲在 DELPHI 中有動態的類的類型信息,有真正虛擬的類方法,以及構造函數是基於類實現的等等這些關鍵概念,纔可實現虛擬的構造函數。對象是由類產生的,對象就好象成長中的嬰兒,而類就是它的母親,嬰兒本身的確不知道本身未來會成爲何樣的人,但是母親們卻用各自的教育方法培養出不一樣的人,道理是相通的。

都知道強大的 VCL 是 DELPHI 得以成功的基礎之一,而全部 VCL 的鼻祖是 TComponent 類。在 TComponent 類的定義中,構造函數 Create 被定義爲虛擬的。這能使不一樣類型的控件實現各自的構造方法,這就是 TClass 創造的類之類概念的偉大,也是 DELPHI 的偉大。

第四節 運用 TObject 的方法

咱們先來看看 TObject 的各個方法都是些什麼東東。簡要列示以下:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
constructor Create;
  TObject 類的構造函數,用於創建對象。
 
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 ;
  得到對象類的運行時類型信息(RTTI),通常用於 Tpersistent 類。
 
class function InstanceSize: Longint ;
  得到對象實例的大小。
 
class function InheritsFrom(AClass: TClass): Boolean ;
  判斷對象或類是不是從指定的類派生的。
 
class function MethodAddress( const Name: ShortString ): Pointer ;
  得到對象或類指定方法名稱的調用地址。該方法必須是 published 的。
 
class function MethodName(Address: Pointer ): ShortString ;
  得到對象或類指定方法地址的方法名稱。該方法必須是 published 的。
 
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;
  支持接口對象 safecall 調用異常處理虛方法,常被接口對象重載。
 
procedure AfterConstruction; virtual;
  對象創建後首先被調用的虛方法,供派生類對象重載以初始化新對象。
 
procedure BeforeDestruction; virtual;
  對象釋放前最後被調用的虛方法,供派生類對象重載以清理對象數據。
 
procedure Dispatch( var Message); virtual;
  對象的消息處理方法,支持 Windows 等消息處理。
 
procedure DefaultHandler( var Message); virtual;
  缺省的消息處理方法。
 
class function NewInstance: TObject; virtual;
  分配對象實例空間的虛方法。
 
procedure FreeInstance; virtual;
  釋放對象實例空間的虛方法。
 
destructor Destroy; virtual;
  對象的析構虛方法,用於消滅對象。

 

這些方法在 DELPHI 的幫助文檔中都有描述。有些方法已在前面簡單介紹過。因爲 TObject 的方法也比較多,一時也講不完。就挑一兩個說說其用法吧,其餘的在後面用到時再細細道來也不遲。

就說說 MethodAddress 和 FieldAddress 對象方法吧。

在使用 DELPHI 開發程序的過程當中,咱們常常會與 VCL 的屬性和事件打交道。咱們添加元件到設計窗口中,設置元件的相關屬性,爲元件的各類事件編制處理事件的方法。而後,輕輕鬆鬆地編譯,程序就誕生了,一切都是可視化的。

咱們知道,在設計時 DELPHI 將元件的數據成員(包括字段、屬性和事件)等信息存儲在*.DFM 文件中,並將其做爲資源數據編譯到最終的執行程序中。DELPHI 的編譯過程同時也將源程序中類的結構信息和代碼也編譯到執行程序中,這些信息在運行時能夠由程序訪問的。

DELPHI 的程序在運行時建立的 Form 或 DataModule 等對象時,首先創建該對象。接着,從相應的資源數據中讀取設計時保留的數據成員信息,並使用 FieldAddress 方法獲取數據成員的訪問地址。而後,用設計時定義的值初始化該數據成員。若是是事件,則再調用 MethodAddress 獲取事件處理程序的調用地址,並初始化該事件。這樣就完成了設計時的數據代碼關係到運行時的數據代碼關係的映射,有點兒象動態鏈接過程。

原來,元件的某些的數據成員和方法是能夠用名稱去訪問的,就是使用 FieldAddress 和 MethodAddress 方法。其實,這種功能是在最基礎的 TObject 中就支持的。固然,只有定義爲 published 訪問級別的數據成員和方法纔可使用名稱去訪問,而定義爲 private、protected 和 public 訪問級別的除外。

注意,只有類型是類或接口的數據成員纔可定義爲 published 的訪問級別,方法都是能夠定義爲 published 的。對於從 TPersistent 繼承的那些對象類,若是沒有特別聲明數據成員和方法的訪問級別的,則缺省是 published 的。例如,TForm 類是 Tpersistent 派生下來的,一個典型的 Form 類的定義中,由 DELPHI 的 IDE 自動維護和生成的那些數據成員和方法,缺省都是 published 的。由於,TPersistent 類使用了特殊的{ $M+ }編譯選項。

知道這層內幕以後,咱們也能夠本身使用這些方法來實現一些有意義的功能。

第五節 對象的消息處理機制

TObject 的定義中,有兩個方法值得咱們注意,就是:

 

1
2
procedure Dispatch( var Message); virtual;
procedure DefaultHandler( var Message); virtual;

 

這兩個方法是 DELPHI 的 VCL 強大的消息處理機制的基礎,Windows 的各類消息最終都是經過這兩個個方法處理掉的。

在講述這一問題以前,有必要先說明一下什麼是消息。

從廣義上將,消息就是信息的傳遞,一個對象將本身知道的事情通知其餘對象。每一個對象能夠根據獲得的消息作出相應的反應。消息在現實世界中廣泛存在,故事、新聞、命令、報告等等,固然也包括流言蜚語。在程序中表現爲數據訪問、過程調用、方法調用、事件觸發和通信協議等等。

而咱們今天討論的消息是狹義的消息,這種消息就是對象間的一種通信協議。這種消息溝通機制的特色是,相關對象之間不會象變量訪問和方法調用那樣是固定的耦合關係,而是很是自由和鬆散的關係。採用這種消息機制,對象之間的通信方式是統一的,而消息的內容是多種多樣的,一組通信的對象之間能夠約定本身的消息格式和含義。雖然,一個對象能夠和將消息發送給任何對象,也能夠接收任何對象發來的消息,但對象通常只處理和發送本身關心的消息。

在 Windows 中的窗口、任務和進程等對象間的信息溝通,都廣泛採用這種消息機制。實際上,消息機制是 Windows 的基礎之一。而 DELPHI 對象的消息處理機制一開始就是爲了支持 Windows 消息而設計的,特別是用於窗口類的控件(即從 TWinControl 繼承的控件)。但這種消息機制已經可以讓全部的 TObject 對象採用這種方式通信。如,咱們熟悉的 TLabel 雖然不是一個窗口控件,但仍然能收到 Windows 發來的消息。固然,Windows 是不會給一個 TLabel 發送消息的,那是 DELPHI 幫的忙。而 TObject 的 Dispatch 方法在這一個過程當中起了關鍵性做用。

咱們知道,DELPHI 將 Windows 的消息描述爲是一個聯合結構,也叫變體結構。消息結構的第一個成員是一個四字節的整數,是區分消息類別的標識。其他的數據成員是根據消息類別的不一樣而有不一樣的定義。正是由於其他的成員是能夠自由定義的,才使得消息處理機制有良好的擴展性。要知道,Windows 有幾千種不一樣類型的消息,DELPHI 也本身擴展了若干種消息。隨着軟件版本的發展,消息的種類還會不斷增長。

關心某種消息的對象類會爲指定的消息定義一個消息處理方法,消息處理方法是用保留字 message 來聲明的。例如:

 

1
2
3
4
5
TMouseObject = class (TObject)
public
   procedure WMMouseMove( var Msg:TMessage); message WM_MOUSEMOVE;
   procedure WMLButtonDown( var Msg:TMessage); message WM_LBUTTONDOWN;
end ;

 

DELPHI 的編譯器將根據 message 保留字識別消息處理方法,並生成一個消息標識到該對象方法的映射表,鏈接到最終的執行程序中。事實上在 DELPHI 的內部,消息處理方法是用 dynamic 方法的機制實現的。前面咱們說過,dynamic 類型的方法是 DELPHI 的另外一種虛方法,是能夠重載以實現對象類的多態性。事實上,dynamic 方法就是根據方法的序號找到調用地址的,這與根據消息 ID 找到各自的消息處理地址是沒有什麼本質區別的。所以,消息處理方法是能夠由子類重載的,這可讓繼承的對象實現本身的消息處理。不過,這種重載的語義與 dynamic 的重載有些不一樣。消息處理方法是按消息標識來重載的,即按 message 保留字後面的值。雖然,子類的消息處理方法的名稱能夠不一樣,只要消息標識相同便可實現重載。例如:

 

1
2
3
4
5
TNewMouseObject = class (TMouseObject)
public
   procedure MouseMove( var Msg:TMessage); message WM_MOUSEMOVE;
   procedure MouseDown( var Msg:TMessage); message WM_LBUTTONDOWN;
end ;

 

其中,MouseMove 方法重載了父類的 WMMouseMove 方法,而 MouseDown 重載了 WMLButtonDown 方法。固然,你也能夠徹底按 dynamic 的語義來定義重載:

 

1
2
3
4
5
TNewMouseObject = class (TMouseObject)
public
   procedure WMMouseMove( var Msg:TMessage); override;
   procedure WMLButtonDown( var Msg:TMessage); override;
end ;

 

雖然,這沒有任何錯誤,但咱們不多這樣寫。這裏只是要向你們說明 message 與 dynamic 的本質相同之處,以加深印象。

根據消息 ID 找處處理該消息的方法地址,就是所謂的「消息分發」,或者叫「消息派遣」,英文叫「Dispatch」。因此,TObject 的 Dispatch 方法正是這個意思!只要你將消息傳遞給 TObject 的 Dispatch 方法,它將會正確地找到該消息的處理方法並交給其處理。若是,Dispatch 方法找不處處理該消息的任何方法,就會調用 DefaultHandler 虛方法。雖然,TObject 的 DefaultHandler 沒有作任何事,但子類能夠重載它以便本身處理漏網的消息。

Dispatch 方法有一個惟一的參數 Message,它是 var 的變量參數。這意味着能夠經過 Message 參數返回一些有用的信息給調用者,實現信息的雙向溝通。每個消息處理方法都有一個惟一的參數,雖然參數類型是不同的,但必須是 var 的變量參數。

值得注意的是,Dispatch 的 Message 參數是沒有類型的!

那麼,是否是任何類型的變量均可以傳遞給對象的 Dispatch 方法呢?

答案是確定的!

你能夠將 integer、double、boolean、string、variant、TObject、TClass……傳遞給一個對象的 Dispatch 方法,編譯都不會出錯。只不過 DELPHI 可能找不到這些東東對應的消息處理方法,即便碰巧找到,可能也是牛頭不對馬嘴,甚至產生運行錯誤。由於,Dispatch 老是將 Message 參數的頭 4 個字節做爲消息 ID 到 dynamic 方法表中尋找調用地址的。

爲何 DELPHI 要這樣定義 Dispatch 方法呢?

由於,消息類型是多種多樣的,消息的大小和內容也是各不相同,因此只能將 Dispatch 方法的 Message 參數定義爲無類型的。固然,DELPHI 要求 Message 參數的頭 4 個字節必須是消息的標識,但編譯器並不檢查這一要求。由於,這可能會擴充 Object Pascal 的語法定義,有些得不償失,也許未來會解決這個問題。

一般,消息被定義爲一個結構。這個結構的頭 4 個字節被定義爲消息標識,其他部分能夠自由定義,大小隨意。Windows 的消息結構大小是固定的,但 DELPHI 能夠容許定義任意大小的消息結構。雖然非 Windows 要求的固定大小消息結構可能沒法用於 Windows 系統的消息傳遞,但對於咱們在程序模塊間定義本身的消息應用來講,倒是很是方便的。

第六節 天蒼蒼,野茫茫

說了半天,咱們已經瞭解到 DELPHI 原子世界的一個大概,也對 DELPHI 的最基礎的一些東西有了必定的輪廓。這對於從此的學習和開發來講是很是有好處的,由於,咱們畢竟知道了一些東西在內部是怎樣實現的。

固然,還有許多東西咱們尚未討論,如類結構中的那些數據又指向什麼地方?運行時刻信息(RTTI) 又是什麼結構?要把這些東東都討論完,咱們還須要進行更多的探索,恐怕最後的結果能夠寫一本厚厚的書。在從此的學習中,咱們會再涉及到其中的內容。

真但願有一天咱們可以完全瞭解 DELPHI 原子世界的全部奧祕。但這彷佛是不可能的,由於 DELPHI 還在不斷髮展,新的技術會不斷的引入。所以,咱們不追求最終的結果,探索的過程每每比最終的結果更幸福。

只要不斷的努力,相信有一天,咱們能上升到另外一更高的思想境界。那時,咱們將更加充實,世界在咱們眼裏將變得更美麗。雖然,天仍是那樣的藍,大地仍是那樣的綠,但咱們的心情又會怎樣呢?

「天蒼蒼,野茫茫,風吹草低見牛羊」。

相關文章
相關標籤/搜索