線上訂房服務-台灣趴趴狗聯合訂房中心
發文 回覆 瀏覽次數:2256
推到 Plurk!
推到 Facebook!

Delphi.NET 內部實現分析

 
阿子
站務副站長


發表:120
回覆:230
積分:201
註冊:2002-03-18

發送簡訊給我
#1 引用回覆 回覆 發表時間:2003-03-17 01:13:53 IP:61.64.xxx.xxx 未訂閱
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 HelloWorld
HelloWorld名字空間中只定義了一個Unit類。Delphi.NET中,每一個單元(Unit)都在自己的命名空間下有一個自動生成的Unit類,用於實現總體變數、全局函數等等特性。 因為CLR中是不存在獨立於類之外的元素的,所有元素都必須以類形式組織,所以對Delphi.NET/C 之類支援總體變數、函數的語言,必須用一個自動生成的偽類來包裝。Delphi.NET為每個單元生成一個Unit類,Managed C 等語言中則使用其他諸如之類的名字。 因為我們的HelloWorld.dpr中沒有定義自己的類,直接使用函數,所以在HelloWorld命名空間中,只有一個Unit類,內容如下
.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
系統時間:2024-06-16 15:59:49
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!