Delphi.NET 内部实现分析(草稿)(0分)

F

flier

Unregistered / Unconfirmed
GUEST, unregistred user!
晚上把前段时间对Delphi.NET的实现原理的分析大概整理了一下
想写一个系列文章介绍一下,不知有没有人感兴趣想看看啊?

Delphi.NET 内部实现分析(草稿)

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++等语言中则使用其它诸如<Module>之类的名字。
因为我们的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在类的内存布局上的不同。
 
还有吗??接着贴,,
 
不错啊. 继续.. :)
 
太好了,收藏!
继续呀!
 
楼主辛苦了

go on.....
 
楼主辛苦了:)
 
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:TObject
Cls:TClass): TObject;
begin
Result := Obj;
if not _IsClass(Obj, Cls) then
raise System.FormatException.Create('Invalid Cast');
end;
//-----------------------------------------Borland.Delphi.System.pas--
as操作符的实现,只是简单的赋值加检测而已,因为CLR是单根结构,所以转换总是成功的,
只需在转换后用is操作符检测,抛出异常情况就行。
//-----------------------------------------System.pas--
function _AsClass(Child: TObject
Parent: TClass): TObject;
{$IFDEF PUREPASCAL}
begin
Result := Child;
if not (Child is Parent) then
Error(reInvalidCast)
// loses return address
end;
//-----------------------------------------System.pas--
可以看到Delphi中的实现也是非常类似的。

最后一个相关函数是_ClassCreate,用于实现类型的创建与构造。
//-----------------------------------------Borland.Delphi.System.pas--
function _ClassCreate(Cls: TClass
Params: Array of TObject): TObject;
begin
Result := System.Activator.CreateInstance(Cls.SystemType, Params);
end;
//-----------------------------------------Borland.Delphi.System.pas--
与Delphi的System.pas中冗长的_ClassCreate函数实现相比,Delphi.NET无需关心
类的内存获取、构造异常的截获以及Self指针的修正等等,只是简洁的通过System.Activator类
完成所需功能,这就是底层有一个强大完善类库支持的优势所在。
至此,Borland.Delphi.System单元中关于元类、类与对象的相关定义及实现就基本上分析完了,
虽然只有寥寥百来行代码,但它为Delphi在CLR上的映射打下了坚实的基础。
下一节我们将进一步看看Delphi中消息与方法的映射关系是如何在Delphi.NET中模拟的。
 
提一下。。。。。。
 
不错!继续!
 
强!顶!
期待下篇发布!
 
我记的java早就有class helper,功能也相似,这不是borland的原创吧。
还有type interface
 
顶部