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

嗯,不错。
 

2.4 消息

对于类的可重载方法而言,最常见的实现方法是构建一张VTable表,每个方法占一个slot。
但这种处理方法受到空间和时间上的限制,在处理大量方法如众多窗口消息的处理方法时有局限性。
为处理这个矛盾,MFC使用宏定义一套独立于类的消息处理函数表,ATL干脆要求编译器在合适
时候不使用VTable以此来进行优化。而Delphi则通过提供类似于虚拟方法的动态方法
dynamic method来解决这个矛盾。
虚方法virtual method保存在VMT中,生成代码为使用效率进行优化,适合完成普通的类继承
重载方法的实现;动态方法则根据一个编号保存在一张单独的表中,生成代码为程序代码大小优化,
适合实现由祖先类定义的大量可重载但并不经常被使用的方法,最典型的例子就是Windows消息处理函数。
因此我们在定义一个Windows消息处理函数时,后面会跟一个message关键字,指定动态方法编号。
//-----------------------------------------Forms.pas--
type
TScrollingWinControl = class(TWinControl)
begin
procedure WMSize(var Message: TWMSize)
message WM_SIZE;
...
//-----------------------------------------Forms.pas--
也可以使用dynamic关键字定义动态方法,由系统自动分配方法编号。
在Delphi中,这张动态方法表的指针保存在VMT的vmtDynamicTable域中,
动态方法被调用时会使用TObject.Dispatch方法调用GetDynaMethod函数
从动态方法表中,根据传入消息结构的第一个字段编号,查找编号符合的动态方法,
如果不存在匹配的编号,则调用TObject.DefaultHandler虚方法处理异常情况。
而在Delphi.NET中,Borland无法再使用VMT用作数据存储,但为了提供源代码级
兼容性,只能设法模拟Delphi中的语义。
好在CLR提供了特性Attribute这种非常好用而且有用的功能,能够将任意的静态或
动态的数据及功能附加到任意的语法元素上。具体原理和使用请参考前面提过的书籍和文章。
Delphi.NET在预览版中,对Attribute的支持还很弱,不过就我测试,在2002-11-14
的测试版本中,已经可以不只局限于Delphi.NET文档中所说只能用于类型了,估计正式版
发布时应该能够提供完整的支持。
Delphi.NET中对动态方法的模拟就是通过MessageMethodAttribute完成的。
当用户使用dynamic定义动态方法,或者使用message定义消息处理函数时,编译器
会自动给此方法添加一个[MessageMethodAttribute(1)]特性,并指定编号。
而TObjectHelper.Dispatch则通过枚举类型所有方法,找出带有MessageMethodAttribute
特性的动态方法,根据MessageMethodAttribute.ID判断应该调用的动态方法。
如果没有找到则还是调用TObject.DefaultHandler方法处理异常。为了提高查找效率,
Borland.Delphi.System单元中还实现了一个缓存,使用Hash表MethodMaps
将搜索过的类与其动态方法列表缓存起来,下次使用直接可以定位获取。
//-----------------------------------------Borland.Delphi.System.pas--
type
MessageMethodAttribute = class(TCustomAttribute)
private
FID: Integer;
public
constructor Create(AID: Integer);
property ID: Integer read FID;
end;

TObjectHelper = class helper for TObject
...
procedure Dispatch(var Message);
end;
//-----------------------------------------Borland.Delphi.System.pas--
MessageMethodAttribute特性定义供内部使用,在dyanic或message关键字
被使用时将自动附加到相应的动态方法上。如

procedure SayHello(var Message: TMessage)
dynamic;

procedure SayHello(var Message: TMessage)
message 1;

[MessageMethodAttribute(1)]
procedure SayHello(var Message: TMessage);

这几种写法基本上是等价的,只不过dynamic关键字不指定动态方法编号。
//-----------------------------------------Borland.Delphi.System.pas--
function GetMessageID(Obj: TObject): Integer;
var
Field: FieldInfo;
begin
Field := Obj.GetType.GetFields[0];
Result := Integer(Field.GetValue(Obj));
end;
//-----------------------------------------Borland.Delphi.System.pas--
对TObject.Dispatch而言,参数Message可变,但第一个字段必须是一个Integer,
指明此Message需要被Dispatch到哪个编号的动态方法。GetMessageID函数就负责
用Reflection从一个类型中获取消息编号。注意这里并没有做异常情况检测,意味着如果
传递无效的类型进来可能导致异常。
TObjectHelper.Dispatch代码主要是通过在MethodMaps中查表定位动态方法,
如果没有缓存就使用TMethodMap.Create构造类型与动态方法的映射关系,代码略过。
 
不错。不过如果Borland的帮助文件如果再不改善,估计最终会被MS吃掉。
 
2.5 其它

在了解了Borland.Delphi.System中的几个重要部分之后,剩下的就是一些零零碎碎的扫尾工作。

2.5.1 类型别名

为兼容Delphi中的特有类型,Borland.Delphi.System单元中定义了很多类型别名。
如我们前面分析过的TObject就是System.Object的别名。
//-----------------------------------------Borland.Delphi.System.pas--
type
TDateTime = type Double;
Extended = type Double
// 80 bit reals are unique to the Intel x86 architecture
Comp = Int64 deprecated;

TGUID = packed record
D1: LongWord;
D2: Word;
D3: Word;
D4: array[0..7] of Byte;
end;
//-----------------------------------------Borland.Delphi.System.pas--
对于Delphi的TDateTime类型来说,它在实现上是以一个Double即8字节浮点数存储的,
兼容OLE自动化中的时间格式。在Delphi.NET中继承了这一存储方式,而没有直接使用BCL
提供的System.DateTime结构,不过仍然可以使用DateTime.FromOADate和
DateTime.ToOADate方法在System.DateTime和TDateTime之间双向转换。
格式存储说明如下(from MSDN)

OLE 自动化日期以浮点数形式实现,其值为距 1899 年 12 月 30 日午夜的天数。
例如,1899 年 12 月 31 日午夜表示为 1.0;1900 年 1 月 1 日早晨 6 点表示为 2.25;
1899 年 12 月 29 日午夜表示为 -1.0;1899 年 12 月 29 日早晨 6 点表示为 -1.25。
只有刻度值大于或等于正的或负的 31241376000000000 的 DateTime 对象才可以表示为
OLE 自动化日期。未初始化的 DateTime(即刻度值为 0 的实例)将转换为等效的未初始化
OLE 自动化日期(即值为 0.0 的日期,它表示 1899 年 12 月 30 日午夜)。

而Extended和Comp则只是一个简单的别名。TGUID也只是一个简单的重定义。
值得注意的是这里的packed关键字。在CLR中,类的成员的物理位置对程序本身是没有意义的,
CLR可以任意安排字段的位置以进行字节对齐等等优化操作。而为了与现有代码进行交互,CLR提供了
StructLayoutAttribute属性允许限定类型的内部物理结构。在Delphi.NET中可以通过packed
关键字定义此结构的成员必须按定义的次序在内存中排列,即LayoutKind.Sequential的形式。
而在Delphi.NET中,所有的record在实现上都是ValueType的子类,即值类型,直接在堆栈上操作。

2.5.2 异常

同TObject一样,Delphi中异常类继承链的根Exception在Delphi.NET中,也只是BCL的异常类
System.Exception的一个别名,而只是通过class helper提供源代码级兼容性。
//-----------------------------------------Borland.Delphi.System.pas--
Exception = System.Exception;
ExceptionHelper = class helper for Exception
private
class function CreateMsg(const Msg: string): Exception;
function GetHelpContext: Integer;
procedure SetHelpContext(AHelpContext: Integer);
public
// Doc: The help context return zero(0) if exception's helplink property
// cannot be parsed into an integer.
property HelpContext: Integer read GetHelpContext write SetHelpContext;

// constructor Create(const Msg: string) is provided by the CLR class
class function CreateFmt(const Msg: string
const Args: array of const): Exception;
class function CreateHelp(const Msg: string
AHelpContext: Integer): Exception;
class function CreateFmtHelp(const Msg: string
const Args: array of const;
AHelpContext: Integer): Exception;
end;
ExceptionClass = class of Exception;

EConvertError = class(Exception);

threadvar
_ExceptObject: TObject;

function ExceptObject: TObject;
//-----------------------------------------Borland.Delphi.System.pas--
几个类函数Create*(...)都只是对System.Exception构造函数的包装而已,
用于保存异常相关帮助文件路径的System.Exception.HelpLink属性,则被Delphi.NET
用于保存HelpContext,因为在VCL中,所有的异常都是共用一个帮助文件TApplication.HelpFile。
而ExceptObject函数则由编译器支持,提供访问当前被抛出的异常的手段。此函数在Delphi中
通过VCL维护的SEH异常链获取,而在Delphi.NET中只好由编译器在异常被截获后手动赋值给线程局部存储变量
_ExceptObject,然后再由ExceptObject函数读出,这只是语法一级兼容Delphi而已。
不过这个预览版好像没有提供threadvar的支持,只是把它简单的放到Borland.Delphi.System单元的
全局变量中,作为自动生成Unit类的一个静态成员变量而已,并非线程安全!在Borland.Delphi.Classes
中甚至直接把TThread的定义注释掉,实现不提供。估计还在开发中……sigh

2.5.3 断言(Assert)

断言负责在调试模式下检测一个条件是否成立,失败则引发异常。
//-----------------------------------------Borland.Delphi.System.pas--
interface

{ debugging functions }

procedure _Assert(const Message, Filename: AnsiString
LineNumber: Integer);

type
EAssertionFailed = class(Exception)
public
ShortMessage: string;
Filename: string;
LineNumber: Integer;
end;

resourcestring
SAssertionFailed = '%s (%s at %d)';

implementation

procedure _Assert(const Message, Filename: AnsiString
LineNumber: Integer);
var
LException: EAssertionFailed;
begin
{ TODO : Should we be using System.Diagnostics.Debug.Assert/Fail? }
{$MESSAGE WARN 'Assert doesn''t use CreateFmt because it returns the wrong type'}
LException := EAssertionFailed.Create(Format(SAssertionFailed, [Message, Filename, LineNumber]));
LException.ShortMessage := Message;
LException.Filename := Filename;
LException.LineNumber := LineNumber;
raise LException;
end;
//-----------------------------------------Borland.Delphi.System.pas--
_Assert函数的定义基本上是EAssertionFailed异常的一个简单包装。因为Delphi没有提供类似
C++中__FILE__、__LINE__之类的预定义宏,故而只能由编译器在用户使用到Assert函数时,
将当前文件名、行号等调试信息编译进代码中,即在编译器一级提供断言实现。

//-----------------------------------------Borland.Delphi.System.pas--
function Assigned(const AGCHandle: GCHandle): boolean;
begin
Result := AGCHandle.IsAllocated;
end;
//-----------------------------------------Borland.Delphi.System.pas--

//-----------------------------------------GCHandle.cs--
namespace System.Runtime.InteropServices {
public struct GCHandle {
private IntPtr m_handle;

public bool IsAllocated { get { return m_handle != IntPtr.Zero
} }
}
}
//-----------------------------------------GCHandle.cs--
Assigned则是对一个引用类型变量进行检测,与Delphi类似,Delphi.NET中直接通过检测引用类型值
是否为空(null)判断是否有效,但对于值类型则将之与0进行比较。

2.5.4 随机数

Delphi.NET中的随机数基本上是对BCL相关类的一个简单包装,而BCL的随机数算法与VCL一样弱智,
简单的功能还凑合,BCL的System.Security.Cryptography.RandomNumberGenerator相比之下
随机性就好得多,不过要付出速度上的代价。

//-----------------------------------------Borland.Delphi.System.pas--
interface

{ random functions }

var
RandSeed: LongInt = 0;

procedure Randomize;

function Random(const ARange: Integer): Integer
overload;
function Random: Extended
overload;

implementation

var
LastRandSeed: Integer = -1;
RandomEngine: System.Random;

procedure InitRandom;
begin
if LastRandSeed <> RandSeed then
begin
if RandSeed = 0 then
RandomEngine := System.Random.Create
else
RandomEngine := System.Random.Create(RandSeed);
LastRandSeed := RandSeed;
end;
end;

procedure Randomize;
begin
LastRandSeed := -1;
RandSeed := 0;
end;

function Random(const ARange: Integer): Integer;
begin
InitRandom;
Result := RandomEngine.Next(ARange);
end;

function Random: Extended;
begin
InitRandom;
Result := RandomEngine.NextDouble;
end;
//-----------------------------------------Borland.Delphi.System.pas--
 
看来,楼主是一个delphi高手
 
不好意思,实在想不到有什么值得说的了,只好草草结束了 [:)]

2.5.5 其它.其它

Borland.Delphi.System单元虽然比Delphi中的System单元小的多,
但其中也充斥着大量常用但是实现代码枯燥的函数。如

数字处理函数集
字符串处理函数集
命令行信息获取函数集(CmdLine/ParamCount/ParamStr)
格式化输出函数集(Format等)
文本文件(即Text类型,而File类型文件不提供支持)开/关/读/写等函数集
动态数组管理(System.Array类型的简单包装)
当前路径及目录操作函数集
集合类型(CLR中并无集合概念,Set实现上是字节数组的简单包装)
其它一些杂项函数
等等等等

这些零散代码基本上都是对BCL相应功能的简单包装,这里就不一一详述了。

2.5.6 小结

至此,对Delphi.NET中核心单元Borland.Delphi.System单元的介绍
就告一段落了。通过对此单元的分析,我们大致了解了Delphi.NET中对于Delphi
一些核心概念的实现或模仿思路,但不排除在正式版中实现有所改变。


题外话:

首先感谢大家的热心支持,这是督促我这个懒人写完文章(哪怕是草草结束)的最大动力,
也希望这篇文章能够对大家了解即将到来的Delphi.NET、迎接.NET时代有所帮助。
这个系列文章到这里估计也就暂时告一段落了,因为时间仓促、准备不足而且
笔者水平有限,只涉及到Delphi.NET在实现上与Delphi不同的部分内容,
与Delphi.NET的改变来说只是冰山一角而已。本来还想扩大一点分析面,
但考虑到Delphi.NET中RTL其它单元大多只是对原有Delphi代码的BCL封装移植
技术难度并不大,对Delphi熟悉的读者直接阅读源程序可能比看我的文章更容易一些。
因此在分析完涉及到一些底层只是的Borland.Delphi.System后就此打住,
虽然有些虎头蛇尾之嫌,但总免得背画蛇添足之骂名 :)
至于构建在Delphi.NET的RLT之上的应用层架构VCL和以后可能要支持的CLX,
我就没有太多精力写文章介绍了。因为就目前实现的VCL代码来看,只是将以前的VCL代码
managed化而已,实现上还是使用Windows那套传统API管理窗口,与BCL的
System.Windows.Forms.Form根本不搭界。这样一来在Delphi.NET中又多了一个选择
VCL or CLX or System.Windows.Forms.Form...sigh,是好是坏只能待时间评判。
文中如果有解释不够清楚的地方,大家可以跟贴提出,我再详细介绍。也欢迎来信
于我讨论Delphi.NET和CLR相关问题。(flier_lu@sina.com.cn)
再次感谢大家的支持!:)
 
偶像,佩服~~~
建议版主给加到新闻里.
 
就座听课
 
楼主真的很厉害!
佩服佩服!
 
所以我有一个观点,当某一天真的到了.NET时代,我们就不应该要用Delphi了,
只能选C#
 
顶部