Delphi.NET 內部實現分析 |
|
阿子
站務副站長 發表:120 回覆:230 積分:201 註冊:2002-03-18 發送簡訊給我 |
Delphi.NET 內部實現分析
作者:flier
來源:大富翁論壇
0.概述 自M$發佈.NET以來,業界廠商態度大相徑庭。但不可否認的是,
在M$雄厚實力和充足資金的保障下.NET架構已經逐漸站穩腳跟,
開始向Java等既得利益者發起衝擊。
而作為開發工具領跑者的Borland公司,也於2002年末,
伴隨其並沒有太大新意的Delphi 7,一同發佈了Delphi.NET預覽版。
本文就是基於這個預覽版本,針對其內部實現原理進行分析,
幫助讀者在瞭解Delphi的基礎上,過渡到.NET時代。 1. Hello Delphi! 1.1. 初見 Delphi.NET 在詳細分析Delphi.NET實現之前,讓我們先從感性上認識一下它:
//-----------------------------------------HelloWorld.dpr-- Program HelloWorld; {$APPTYPE CONSOLE} begin Writeln('Hello Delphi!'); end. //-----------------------------------------HelloWorld.dpr--看上去是否很眼熟?沒錯,這是一個標準的Delphi控制臺程式, 同時也可以直接使用Delphi.NET編譯成一個真正的.NET程式 E:\Borland\Delphi.Net\demos\Hello>dccil HelloWorld.dpr Borland Delphi Version 16.0 Copyright (c) 1983,2002 Borland Software Corporation Confidential pre-release version built Nov 14 2002 17:05:31 HelloWorld.dpr(8) 9 lines, 0.28 seconds, 4816 bytes code, 0 bytes data. E:\Borland\Delphi.Net\demos\Hello>peverify HelloWorld.exe Microsoft (R) .NET Framework PE Verifier Version 1.0.3705.0 Copyright (C) Microsoft Corporation 1998-2001. All rights reserved. All Classes and Methods in HelloWorld.exe Verified E:\Borland\Delphi.Net\demos\Hello>HelloWorld Hello Delphi! 先使用 dccil,Borland經典Delphi編譯器DCC32的IL版本, 將Delphi的Object Pascal程式碼編譯成.NET架構的IL中間語言檔, 類似Java中的ByteCode; 再使用.NET Framework SDK附帶的peverify驗證程式有效性, 確認此HelloWorld.exe的確是.NET相容的程式; 最後運行HelloWorld.exe程式,得到我們期望的結果。 看上去很美……不是嗎? 不過在這看上去簡單的程式碼背後,Borland做了大量底層工作,提供Delphi在.NET架構上最大限度的來源程式碼級相容性,甚至不惜增加新語言特性(Delphi從Delphi3/4開始,語法上就已經很穩定了,或者說已經很成熟了)。 1.2. 結構 用.NET Framework SDK附帶的ILDASM工具打開HelloWorld.exe文件, 可以看到有兩個名字空間Borland.Delphi.System和HelloWorld存在, 而MANIFEST裏面是配件(Assembly)一級資訊,詳細含義請參加筆者另一系列文章《MS.Net CLR擴展PE結構分析》,裏面有詳細解析。(.NET是M$的.NET架構的名稱,在實現一級其程式運行在CLR Common Language Runtime的環境中,類似Java中虛擬機VM的概念,因此下文中對實現一級不再以.NET而以CLR稱呼)與Delphi一樣,Delphi.NET自動引用System單元的內容,只不過單元名稱變成了Borland.Delphi.System而已。這是Object Pascal增加的語言特性之一--命名空間(namespace),用於將類定義隔離在不同作用域中,以避免名字衝突。 而HelloWorld命名空間則是根據專案或單元名稱自動生成的,實際程式碼一般保存在此命名空間的類中。 我們先來看看程式碼所在的HelloWorld名字空間 .namespace HelloWorld { .class /*0200000B*/ public auto ansi Unit extends [mscorlib/* 23000001 */]System.Object/* 01000001 */ { ... } // end of class Unit } // end of namespace HelloWorldHelloWorld名字空間中只定義了一個Unit類。Delphi.NET中,每一個單元(Unit)都在自己的命名空間下有一個自動生成的Unit類,用於實現總體變數、全局函數等等特性。 因為CLR中是不存在獨立於類之外的元素的,所有元素都必須以類形式組織,所以對Delphi.NET/C 之類支援總體變數、函數的語言,必須用一個自動生成的偽類來包裝。Delphi.NET為每個單元生成一個Unit類,Managed C 等語言中則使用其他諸如 .namespace HelloWorld { .class /*0200000B*/ public auto ansi Unit extends [mscorlib/* 23000001 */]System.Object/* 01000001 */ { .method /*06000027*/ private hidebysig specialname rtspecialname static void .cctor() cil managed .method /*06000025*/ public hidebysig specialname rtspecialname instance void .ctor() cil managed .method /*06000026*/ public hidebysig static void $WakeUp() cil managed .method /*06000023*/ public static void Finalization() cil managed .method /*06000024*/ public static void HelloWorld() cil managed } }其中.cctor()和.ctor()函數分別是類Unit的靜態/動態構造函數,負責對Unit 進行類和物件一級的構造工作。 對Delphi來說,構造函數所在級別是介於類和物件之間的。 TMyClass = class public constructor Create; end; var Obj: TObject; begin Obj := TMyClass.Create; try //... finally FreeAndNil(Obj); end; end;這裏的Create由constructor關鍵字定義為構造函數,實現上類似類函數 (class function),隱含傳入一個指向其元類(MetaClass)的指標和一個標誌, 相對的普通成員函數隱含傳入一個指向其實例的指標,也就是眾所周知的Self。 而在構造函數中,調用時類似調用類函數傳入其元類指標,函數中由編譯器自動添加_ClassCreate函數調用TObject.NewInstance分配記憶體並初始化物件, 將Self改為指向真正的物件。 在Delphi.NET中,.ctor()類似於Delphi中的構造函數,用於在分配記憶體空間後構造物件實例,其被調用時,物件所需記憶體已經分配完畢,所有成員變數被初始化為0、false或null。也就是說Delphi中構造函數的功能在CLR中被分為兩部分,分配和初始化記憶體功能由IL指令newobj完成,調用用戶程式碼初始化物件功能由其構造函數被newobj或initobj指令調用完成,前者用於在堆中分配並初始化對象,後者用於調用構造函數初始化在堆疊上分配好的記憶體。 而.cctor()則是CLR中特有的靜態構造函數概念,其程式碼由CLR保證在此類的任意成員被使用之前調用,初始化類的靜態成員。因而可以存在至多一個不使用參數的靜態構造函數。 因為.ctor()和.cctor()函數都是有CLR內部使用的,因而定義其函數標誌為 hidebysig specialname rtspecialname,分別表示此函數以函數整個定義而並非 僅僅以名字來區分,避免命名衝突;函數名對系統和CLR有特殊意義。 詳細介紹請參加筆者另一系列文章《MS.Net CLR擴展PE結構分析》,裏面有詳細解析。 Unit類中剩下三個方法$WakeUp()由Delphi.NET內部使用;Finalization()完成 類似Delphi單元中finalization節的功能;最後一個HelloWorld()函數也是自動生成,使用Unit類所在名字空間名稱命名,如這裏的HelloWorld,完成類似Delphi單元中initialization節的功能,如在.dpr中則完成begin end.之間的程式功能。 從CLR角度來看,在載入Delphi.NET編譯的配件後,通過Metadata定位到缺省指向的類調用類的靜態構造函數(即本例中的HelloWorld.Unit..cctor()函數),調用類的Main函數(即本例中的HelloWorld.Unit.HelloWorld()方法)。 在程式入口類的靜態構造函數.cctor中,要完成掛接Unit析構函數(本例中 HelloWorld.Unit.Finalization()函數)到系統一級終結調用列表上的工作。 HelloWorld.Unit..cctor中的程式碼如下 unit HelloWorld type Unit = class procedure .cctor(); proceudre HelloWorld(); ... implementation procedure Unit..cctor(); begin Borland.Delphi.System.Unit._AddFinalization( new Borland.Delphi.System._FinalizeHandler( null, HelloWorld.Unit.Finalization)); HelloWorld.Unit.HelloWorld(); end;HelloWorld.Unit.Finalization方法是HelloWorld.Unit類的finalization節 程式碼所在,用於在單元卸載時析構;Borland.Delphi.System._FinalizeHandler是 Borland.Delphi.System單元中定義的一個事件委託類型(Delegate); Borland.Delphi.System.Unit._AddFinalization則是Borland.Delphi.System單元 的一個全局函數_AddFinalization。 在Borland.Delphi.System單元中可以看到其實現程式碼 //-----------------------------------------Borland.Delphi.System.pas-- type _FinalizeHandler = procedure of object; var OnProcessExit: _FinalizeHandler; procedure _AddFinalization(f: _FinalizeHandler); begin OnProcessExit := _FinalizeHandler(System.Delegate.Combine( System.Delegate(@f), System.Delegate(@OnProcessExit))); end; {$IF SimpleFinalizer} type TFinalObject = class public procedure Finalize; override; end; procedure TFinalObject.Finalize; begin OnProcessExit; inherited; end; {$ELSE} procedure ProcessExitHook(sender: System.Object; eventArgs: System.EventArgs); begin OnProcessExit; end; {$IFEND} {$IF SimpleFinalizer} var _GlobalFinalObject: TObject; {$IFEND} initialization {$IF SimpleFinalizer} {$MESSAGE WARN 'Using simple finalizer'} _GlobalFinalObject := TFinalObject.Create; {$ELSE} System.AppDomain.CurrentDomain.add_ProcessExit(ProcessExitHook); // System.AppDomain.CurrentDomain.add_ProcessExit( // System.EventHandler.Create(nil, IntPtr(@ProcessExitHook))); {$IFEND} //-----------------------------------------Borland.Delphi.System.pas—首先事件委託類型_FinalizeHandler的定義,和Delphi中定義類成員函數指標語法相同。 在Delphi中,此類指標實現上是以一個TMethod結構存在的,分別保存物件實例和成員函數的指標,這與普通C/C 語言中的函數指標大相徑庭。 //-----------------------------------------System.pas-- TMethod = record Code, Data: Pointer; end; //-----------------------------------------System.pas—而在CLR中事件的實現與Delphi非常類似(畢竟是同一個人設計的:),只不過用類包裝了一下罷了,具體講解參見牛人Jeffrey Richter的《MS .NET Framework 程式設計》一書。 因此在Delphi.NET中對事件處理函數的定義可以原封不動。 與Delphi不同的是,CLR中的Deltegate可以同時由多個處理函數訂閱,在C#一類直接支援事件的語言中直接表述為 OnProcessExit = new _FinalizeHandler(...) 即可,而在Delphi.NET中只好用_AddFinalization函數中這類精確的函數調用,希望Borland能在以後給Delphi.NET加上類似C#語言中的表述語法,這樣跟清晰明瞭一些,要是有運算符重載就跟爽了,反正底層都是用CLR實現。 接著Delphi.NET提供了兩種實現單元一級finalization功能的方法 定義SimpleFinalizer的話,就使用較為簡單的方法,直接由_GlobalFinalObject物件管理生命週期。 因為_GlobalFinalObject物件是一個全局物件,其生命期貫串整個程式,當其被釋放時整個程式也就結束了。 而TFinalObject重載了Finalize方法,此方法如果被重載,則GC 垃圾回收在釋放物件之前,會調用此方法。 這樣就保證所有單元的finalization節在Borland.Delphi.System單元卸載之前,通過註冊的析構事件OnProcessExit被依次調用。 如果不定義SimpleFinalizer的話,則使用較複雜的方法。通過ProcessExitHook函數掛接到當前AppDomain 應用程式域的進程結束事件上,在進程結束之前依次調用。 在掛接完析構處理函數後,.cctor會調用HelloWorld()指向單元初始化程式碼或程式執行程式碼。 如在本例中調用HelloWorld.Unit.HelloWorld()函數 public static void HelloWorld() { Borland.Delphi.System.Unit.$WakeUp(); Borland.Delphi.System.Unit._WriteLn( Borland.Delphi.System.Unit._Write0WString( Borland.Delphi.System.Unit.Output, "Hello Delphi!")); Borland.Delphi.System.Unit.__IOTest(); }前後的$WakeUp()和__IOTest()分別負責喚醒和IO測試,目前沒有什麼作用。 中間的程式碼就是Writeln('Hello Delphi!');這行程式碼的實現,等我們具體解析 Borland.Delphi.System單元時再作評述。 1.3 類的實現 在分析了一個最簡單的Delphi.NET程式後,我們來看看複雜一些的例子。 這個例子中定義了一個TDemo類,完成和上個例子相同的功能,只不過在類中完成。 //-----------------------------------------HelloWorld2.dpr-- Program HelloWorld; {$APPTYPE CONSOLE} type TDemo = class public procedure Hello; end; { TMemo } procedure TDemo.Hello; begin Writeln('Hello Delphi!'); end; begin TDemo.Create.Hello; end. //-----------------------------------------HelloWorld2.dpr--用ILDASM打開HelloWorld2.exe,可以發現在HelloWorld名字空間中增加了 一個TDemo類,HelloWorld.Unit.HelloWorld()函數中的程式碼也改為了 public static void HelloWorld() { Borland.Delphi.System.Unit.$WakeUp(); new HelloWorld.TDemo().Hello(); }接著我們來看看TDemo這個類的實現。 我們會發現TDemo是直接從System.Object類繼承出來的。 在傳統的Delphi語言中,如果在定義一個類的時候不顯式指定其父類,則隱式將其父類指定為TObject類;而在Delphi.NET中,因為要讓系統架構融入CLR的標準類庫的架構中,不可能再為Delphi.NET定義一套繼承樹,所以所有TObject都變為了System.Object。為最大限度相容原有程式碼中的TObject,Delphi.NET中引入了class helper這個重要概念。 class helper這個概念可以說是一種非常巧妙的妥協,它允許用戶向現有類樹的結點從外部添加新功能,但限定不能增加資料成員。因為Borland要將其VCL架構移植到CLR的BCL上,雖然BCL和VCL結構上非常類似(本來就是一個人設計的),但從名字到功能都有一些細節上的差異,而Borland沒有BCL的源程式碼,M$也不可能允許其他廠商修改其源程式碼。這就造成了Borland的悖論,要在無法修改BCL架構的情況下修改其架構來支持VCL,呵呵。 妥協的結果就是class helper這種補丁語法的出現。 之所以說是補丁語法,是因為class helper允許在不修改現有類的基礎上,將新功能添加到其上。 而class helper又限定不能增加資料成員,這樣就不會因為改變原有類的物理結構導致程式變動。 這樣的效果形象地說來就是給原有類打上一個補丁,讓原有的BCL的類無論看上去還是使用起來都很像VCL的對應類。 例如在Delphi.NET中,TObject類型實際上就是System.Object的一個別名。 而TObjectHelper作為TObject的補丁,為TObject提供相容VCL中TObject的函數,這些函數實現上都是通過System.Object的方法完成的,只是名字和用法不同。 //-----------------------------------------Borland.Delphi.System.pas-- type TObject = System.Object; TObjectHelper = class helper for TObject procedure Free; function ClassType: TClass; class function ClassName: string; class function ClassNameIs(const Name: string): Boolean; class function ClassParent: TClass; class function ClassInfo: TObject; class function InheritsFrom(AClass: TClass): Boolean; class function MethodAddress(const Name: string): TObject; class function SystemType: System.Type; function FieldAddress(const Name: string): TObject; procedure Dispatch(var Message); end; //-----------------------------------------Borland.Delphi.System.pas—這樣一來,Borland就簡潔但並不完美的解決了這個悖論。不過可以預見,這種語法的出現,必將在將來引發激烈的爭論,因為無論如何,這種語法事實上衝擊了OO設計思想的純潔性。 後面我們分析Borland.Delphi.System單元時再詳細討論class helper的使用方法。 在TDemo類中,另一個值得注意的是名為@MetaTDemo的嵌套子類。 在Delphi中,每個類都有一個對應的元類 MetaClass,可以通過class of TMyClass定義TMyClass的元類類型來訪問,也可以從類方法中直接通過Self指標訪問。元類在實現上就是在此類對象所共有的VMT表。 而在Delphi.NET中,類的記憶體佈局不再由Delphi完全控制,不大可能將VMT再綁定到每個物件上。 所以Borland通過一個以"@Meta 類名"作為類名稱的嵌套子類來表示此類的元類。如TDemo的元類是TDemo.@MetaTDemo類,從Borland.Delphi.System._TClass類繼承出來。 //-----------------------------------------Borland.Delphi.System.pas-- _TClass = class; TClass = class of TObject; _TClass = class protected FInstanceType: System.RuntimeTypeHandle; FClassParent: _TClass; public constructor Create; overload; constructor Create(ATypeHandle: System.RuntimeTypeHandle); overload; constructor Create(AType: System.Type); overload; function ClassParent: TClass; virtual; end; TClassHelperBase = class(TObject) public FInstance: TObject; end; //-----------------------------------------Borland.Delphi.System.pas—所有的元類如TDemo.@MetaTDemo類,都是繼承自_TClass類,並使用類似TClassHelperBase的實現。 如TDemo.@MetaTDemo類就是以類似這樣的偽程式碼定義的,只不過FInstance是靜態成員變數 TDemo = class(TObject) public @MetaTDemo = class(_TClass) public FInstance: TObject; // static class constructor StaticCreate; constructor Create; ... end; class constructor TDemo.@MetaTDemo.StaticCreate; begin FInstance := @MetaTDemo.Create; // normal constructor end; constructor TDemo.@MetaTDemo.Create; begin inherited; inherited FInstanceType := token of HelloWorld.TDemo; end;在@MetaTDemo的靜態構造函數中,將@MetaTDemo.FInstance初始化為自身的實例; 在@MetaTDemo的構造函數中,將其表示類的Token放入_TClass.FInstanceType中,我們後面分析Borland.Delphi.System單元時再詳細解釋。 這一小節我們大概瞭解了Delphi.NET是如何為原有Delphi類在源程式碼一級提供相容性的,分析了class helper和元類 MetaClass的實現原理。下一節我們將開始分析Delphi.NET的核心單元Borland.Delphi.System,瞭解Delphi的基於TObject的單根結構是如何映射到CLR的FCL基於System.Object的單根結構上,並看看幾個我們熟悉的TObject方法的實現,瞭解Delphi和Delphi.NET在類的記憶體佈局上的不同。 2. Borland.Delphi.System 2.1. 簡介 與傳統Delphi程式編譯時默認包含System單元類似,Delphi.NET程式編譯時默認保護了Borland.Delphi.System單元,而此單元中集中了諸多基礎之基礎的類和函數的定義、實現。 與Delphi不同的是,目前Delphi.NET的預覽版中,Borland.Delphi.System還只是包含了相對基本的功能,如TObject類及其相關輔助函數、以及一些基礎的函數,特別是很多帶下劃線首碼的函數,根本就是由編譯器一級固化支援,如標準輸入輸出函數中的WriteLn以及字串處理函數等等。 下面我們來一點點分析這個最基礎的單元:Borland.Delphi.System。 2.2. 元類 語言的發展歷程,就是對問題域的抽象層面的逐漸提升的過程。在Delphi、C#以及Java這些現代語言中,一個很重要的特性就是對RTTI 運行時類型資訊的支援,而且支援將越來越完善。 Delphi在這方面一個實例就是元類的概念的運用,用以對類的資訊進一步抽象。 關於元類的概念以及使用,已經有大量書籍論述過,這裏不再多說,讓我們來看看實現。 在傳統Delphi的Object Pascal語言中,元類在實現上實際上就是一張VMT(Virtual Method Table虛方法表),在System單元的定義中可以詳細看到其含義 //-----------------------------------------System.pas-- { Virtual method table entries } vmtSelfPtr = -76; vmtIntfTable = -72; vmtAutoTable = -68; vmtInitTable = -64; vmtTypeInfo = -60; vmtFieldTable = -56; vmtMethodTable = -52; vmtDynamicTable = -48; vmtClassName = -44; vmtInstanceSize = -40; vmtParent = -36; vmtSafeCallException = -32 deprecated; // don't use these constants. vmtAfterConstruction = -28 deprecated; // use VMTOFFSET in asm code instead vmtBeforeDestruction = -24 deprecated; vmtDispatch = -20 deprecated; vmtDefaultHandler = -16 deprecated; vmtNewInstance = -12 deprecated; vmtFreeInstance = -8 deprecated; vmtDestroy = -4 deprecated; //-----------------------------------------System.pas—普通物件的第一個雙字就是指向其類的VMT的指標,以此將物件、類和元類關聯起來。 這個VMT表是Delphi中類的核心所在,通過它可以在運行時獲取類的絕大部分資訊。 例如在VMT中有一個vmtSelfPtr指標又回指到VMT表頭,我們可以利用這個特性判斷一個指標指向的是否是有效的物件或類。JCL項目中有程式碼如下 //-----------------------------------------JclSysUtils.pas-- function IsClass(Address: Pointer): Boolean; assembler; asm CMP Address, Address.vmtSelfPtr JNZ @False MOV Result, True JMP @Exit @False: MOV Result, False @Exit: end; function IsObject(Address: Pointer): Boolean; assembler; asm // or IsClass(Pointer(Address^)); MOV EAX, [Address] CMP EAX, EAX.vmtSelfPtr JNZ @False MOV Result, True JMP @Exit @False: MOV Result, False @Exit: end; //-----------------------------------------JclSysUtils.pas—通過VMT中其他的域可以完成更多奇妙的功能,如D6開始對SOAP支持在實現上,就是通過VMT動態查表完成SOAP函數調用到Delphi介面的函數調用轉發的。 而在Delphi.NET中,因為Borland無法控制類在記憶體中的組織方式,因而只能通過前面提到的class helper的補丁方式,讓CLR的BCL的System.Object使用上看起來象TObject。這樣的確能夠在很大程度上提供源程式碼級相容性,但對JclSysUtils這樣的Hacker程式碼就無能為力了 :) 首先我們來看看元類的定義與實現 //-----------------------------------------Borland.Delphi.System.pas-- type TObject = System.Object; _TClass = class; TClass = class of TObject; _TClass = class protected FInstanceType: System.RuntimeTypeHandle; FClassParent: _TClass; public constructor Create; overload; constructor Create(ATypeHandle: System.RuntimeTypeHandle); overload; constructor Create(AType: System.Type); overload; function ClassParent: TClass; virtual; end; TClassHelperBase = class(TObject) public FInstance: TObject; end; //-----------------------------------------Borland.Delphi.System.pas—上一節我們大概分析過元類的實現原理。每一個類有一個對應的嵌套子類, 名稱為@Meta首碼加上類名,此類從Borland.Delphi.System._TClass類繼承出來, 在實現上類似TClassHelperBase,只不過FInstance是類一級的靜態成員變數。 .class /*0200000D*/ auto ansi nested public beforefieldinit @MetaTDemo extends Borland.Delphi.System.@TClass/* 02000003 */ { .field /*0400000E*/ public static class HelloUnit.TDemo/* 02000005 *//@MetaTDemo/* 0200000D */ @Instance .method /*06000027*/ private hidebysig specialname rtspecialname static void .cctor() cil managed .method /*06000026*/ public hidebysig specialname rtspecialname instance void .ctor() cil managed .method /*06000008*/ public instance void Hello() cil managed }元類的靜態構造函數如上一節中的@MetaTDemo..cctor()函數,構造一個元類的實例 放入元類的@Instance靜態成員 private static TDemo.@MetaTDemo..cctor() { TDemo.@MetaTDemo.@Instance = new @MetaTDemo..ctor(); } 元類的構造函數則載入自己類型的Token到FInstanceType欄位中。 public TDemo.@MetaTDemo..ctor() : base() { this.FInstanceType = Token of TDemo; }這個Token在CLR中起到索引的作用,用以在Metadata諸多表中定位特定的表項,每種元素如類、方法、欄位、屬性等等都有其自己的Token。在BCL中Token表現為RuntimeTypeHandle類型,用於諸如Type.GetTypeFromHandle之類函數,可以通過Type.TypeHandle獲取。 關於Token的重要性及物理實現上用法請參加前面提到的我對Metadata分析的文章。 元類這裏保存的FInstanceType實際上就是類似於Delphi中VMT指標的索引。 //-----------------------------------------Borland.Delphi.System.pas-- constructor _TClass.Create; begin inherited Create; end; constructor _TClass.Create(ATypeHandle: System.RuntimeTypeHandle); begin inherited Create; FInstanceType := ATypeHandle; end; constructor _TClass.Create(AType: System.Type); begin Create(AType.TypeHandle); end; //-----------------------------------------Borland.Delphi.System.pas-- 可以看到_TClass的幾種形式構造函數,實際上都是圍繞著這個Token做文章。 //-----------------------------------------Borland.Delphi.System.pas-- function _TClass.ClassParent: TClass; begin if not Assigned(FClassParent) then FClassParent := _TClass.Create(System.Type.GetTypeFromHandle(FInstanceType).BaseType.TypeHandle); Result := FClassParent; end; //-----------------------------------------Borland.Delphi.System.pas—而_TClass.FClassParent則是在需要時填充的父類的元類。注意這裏的轉換Token到 CLR中類型的方法。System.Type.GetTypeFromHandle函數和後面要使用到的 System.Type.GetTypeHandle函數完成Type與Token之間的雙向轉換。 因此可以說在Delphi.NET中,元類實際上就是對類的Token的一個封裝。 在瞭解了元類的實現後,我們來看看如何從一個Token獲取其元類。 //-----------------------------------------Borland.Delphi.System.pas-- var MetaTypeMap: Hashtable; function _GetMetaFromHandle(ATypeHandle: System.RuntimeTypeHandle): _TClass; var t: System.Type; begin if not Assigned(MetaTypeMap) then MetaTypeMap := Hashtable.Create; Result := _TClass(MetaTypeMap[ATypeHandle]); if not Assigned(Result) then begin t := System.Type.GetTypeFromHandle(ATypeHandle); t := t.GetNestedType('@Meta' t.name, BindingFlags(Integer(BindingFlags.Public) or Integer(BindingFlags.NonPublic))); if Assigned(t) then begin Result := _TClass(t.GetField('@Instance').GetValue(nil)); MetaTypeMap.Add(ATypeHandle, Result); end else begin Result := _TClass.Create(ATypeHandle); end; end; end; //-----------------------------------------Borland.Delphi.System.pas—Delphi.NET使用一個哈希表MetaTypeMap來緩存Token到元類的轉換關係, 因為這種轉換是耗時且較頻繁的操作,這主要是為優化現有Delphi程式碼對元類的大量使用。 對一個Token,Delphi.NET先將其轉換為一個Type類型,然後取其嵌套子類, 子類名就是@Meta首碼加類名,元類允許是公開或私有。 如果找到元類的類型,則從元類的@Instance欄位獲取值,也就是元類的一個實例;否則使用此Token構造一個新的元類並返回。 這樣Delphi.NET就完成了TClass機制在CLR環境下的映射。 2.3. 對象 接下來我們看看類的實例,物件的實現 //-----------------------------------------Borland.Delphi.System.pas-- type TObject = System.Object; TObjectHelper = class helper for TObject procedure Free; function ClassType: TClass; class function ClassName: string; class function ClassNameIs(const Name: string): Boolean; class function ClassParent: TClass; class function ClassInfo: TObject; class function InheritsFrom(AClass: TClass): Boolean; class function MethodAddress(const Name: string): TObject; class function SystemType: System.Type; function FieldAddress(const Name: string): TObject; procedure Dispatch(var Message); end; //-----------------------------------------Borland.Delphi.System.pas—從Borland.Delphi.System的定義中我們可以看到,TObject實際上就是System.Object的別名而已,而真正的程式碼是通過class helper給TObject也就是System.Object打的補丁。 下面我們來仔細看看TObjectHelper的實現程式碼,理解Delphi是如何移植到CLR上的。 //-----------------------------------------Borland.Delphi.System.pas-- procedure TObjectHelper.Free; begin if (Self <> nil) and (Self is IDisposable) then (Self as IDisposable).Dispose; end; //-----------------------------------------Borland.Delphi.System.pas— 與傳統Delphi的用戶自行管理記憶體模式不同,Delphi.NET使用CLR提供的自動記憶體管理機制,有GC垃圾回收機制自動回收無用的記憶體。 但為了給用戶一個自行控制外部資源(如檔控制碼,網路連接)等稀缺資源的機會,CLR同時提供了IDisposable介面這種妥協機制。一個類如果實現了IDisposable介面,則說明其有需要自己管理生命週期的資源,可以通過IDisposable.Dispose方法手工釋放。介面定義如下 IDisposable = interface procedure Dispose; end;因而Delphi.NET的Free方法只是簡單檢測當前物件是否支援IDisposable介面,如支援則直接調用IDisposable.Dispose釋放資源。不過目前預覽版對此介面的支援好像不是很好用 :( 如果以後能夠支持象C#中using語句那樣的功能就好了,呵呵 在TObjectHelper的實現中可以看到,對類方法如class function方法來說, Self就是指向類的元類,因而ClassParent和ClassInfo方法直接從其元類獲取資訊。 //-----------------------------------------Borland.Delphi.System.pas-- class function TObjectHelper.ClassParent: TClass; begin Result := _TClass(Self).ClassParent; end; class function TObjectHelper.ClassInfo: TObject; begin Result := _TClass(Self).FInstanceType; end; //-----------------------------------------Borland.Delphi.System.pas—而對於普通的方法,則需要將其轉換為Token或Type再進行處理。ClassType和FieldAddress就是兩個很好的例子。前者使用剛剛提到的_GetMetaFromHandle函數從當前物件的類型Token獲取元類;後者則從當前物件的類型Type(CLR中類似元類的概念)獲取指定名稱欄位的物件 //-----------------------------------------Borland.Delphi.System.pas-- function TObjectHelper.ClassType: TClass; begin Result := _GetMetaFromHandle(System.Type.GetTypeHandle(Self)); end; function TObjectHelper.FieldAddress(const Name: string): TObject; begin Result := TypeOf(Self).GetField(Name); end; //-----------------------------------------Borland.Delphi.System.pas-- 剩餘方法的實現原理大同小異,無非是在物件、類、元類、Token和Type之間互相轉換,獲取所需的資訊。 //-----------------------------------------Borland.Delphi.System.pas-- class function TObjectHelper.SystemType: System.Type; begin Result := System.Type.GetTypeFromHandle(_TClass(Self).FInstanceType); end; class function TObjectHelper.ClassName: string; begin Result := System.Type.GetTypeFromHandle(_TClass(Self).FInstanceType).Name; end; class function TObjectHelper.ClassNameIs(const Name: string): Boolean; begin Result := ClassName = Name; end; class function TObjectHelper.InheritsFrom(AClass: TClass): Boolean; begin Result := TypeOf(Self).IsInstanceOfType(TypeOf(AClass)); end; class function TObjectHelper.MethodAddress(const Name: string): TObject; begin Result := TypeOf(Self).GetMethod(Name); end; //-----------------------------------------Borland.Delphi.System.pas—餘下一個TObjectHelper.Dispatch方法,後面分析Delphi.NET消息模型時再詳談。 由此我們可以看出,Delphi.NET中使用了從內嵌子類到class helper種種方法, 才總算解決了從傳統繼承模型和記憶體模型遷移到CLR以及FCL類樹的過程,遷移過程不可謂不艱辛。 雖然這種解決方法不能算是完美,但相信Borland也是在綜合評估了諸多其他手段之後,才做出這樣的選擇,付出了一些代價、如class helper,也取得了不少的成果、源程式碼級相容較強。 這種映射模型到底行不行,我想只能有待時間來做評論。 最後我們來看看Delphi的is和as關鍵字是如何在Delphi.NET中實現的 //-----------------------------------------Borland.Delphi.System.pas-- function _IsClass(Obj:TObject; Cls:TClass): Boolean; var t1, t2: System.Type; begin if not Assigned(Obj) then Result := false else begin t1 := Obj.GetType; t2 := System.Type.GetTypeFromHandle(_TClass(Cls).FInstanceType); if t1 = t2 then Result := true else Result := t1.IsSubclassOf(t2); end; end; //-----------------------------------------Borland.Delphi.System.pas-- _IsClass函數實現很簡單,檢測物件有效性後直接通過判斷兩個類型的繼承關係檢測。 //-----------------------------------------System.pas-- function _IsClass(Child: TObject; Parent: TClass): Boolean; begin Result := (Child <> nil) and Child.InheritsFrom(Parent); end; //-----------------------------------------System.pas—相比之下Delphi的is實現更簡單,直接用TObject.InheritsFrom實現。 Delphi.NET之所以不象Delphi那樣直接使用TObject.InheritsFrom實現is關鍵字, 是因為相對於Type.IsSubclassOf方法來說,TObjectHelper.InheritsFrom方法 使用的Type.IsInstanceOfType方法代價較大。 Type.IsSubclassOf方法只是從傳入類型開始,一級一級查看其父類是否自己。 //-----------------------------------------Type.cs-- public abstract class Type : MemberInfo, IReflect { public virtual bool IsSubclassOf(Type c) { Type p = this; if (p == c) return false; while (p != null) { if (p == c) return true; p = p.BaseType; } return false; } } //-----------------------------------------Type.cs--而Type.IsInstanceOfType則要考慮Remoting、COM、介面以及運行時類型等等 諸多複雜因素,因而不適合用在is/as這樣頻繁使用的關鍵字實現上。 //-----------------------------------------Borland.Delphi.System.pas-- function _AsClass(Obj:TO
------
從思考取勝一切~q |
本站聲明 |
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。 2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。 3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇! |