Riceball
[goto 文章网站声明 ]

著者:Riceball LEE

文章编目 [隐藏]

记录、对象和类的比较

 

类类型   = 对象指针(动态对象)
对象类型 = 特别的unpacked的记录类型,在对象类型中字段可以被继承,并且可以拥有自己的函数。

记录类型

记录类型,在一些语言中被称为一种结构,用来表现一系列的元素。每一个元素称之为一个字段。记录的声明就是定义记录中的每一个字段的名称和类型。

记录默认情况时,存放地址是基于32位的双字对齐的,这样能够得到快速访问。如果你使用保留字packed定义记录着将压缩数据在内存的存储空间,不再对齐数据。记录类型也可以有变体部分,类似于case语句,不过定义变体必须是在其他字段以后!

 

type
recordTypeName = [packed] record
Field1: Integer;
...
fieldn: Integer;
case [tag:] ordinalType of
constantList1: (variant1:Integer; variant2: Integer);
...
constantListn: (variantn: Integer);
end;
var
aRec: recordTypeName;

aRec 记录类型的变量在内存中大概象这样:

 

aRec: field1,fieldn, variant1, variant2

对象类型

对象类型实际上就是一种特殊的记录类型。只不过对象类型中的字段能够被子类所继承,并且可以有自己的函数和字段一起绑定的。但是,注意的是,对象并不支持记录的packed 和变体部分。令人遗憾。

 

type
TMyObj = Object
protected
Field1: Integer;
field2: Integer;
public
procedure mymethod;
end;
TMyChildObj = Object(TMyObj)
protected
Field3: Integer;
end;
var
MyChildObj: TMyChildObj;

MyChildObj 对象变量在内存中的组织如下:

 

MyChildObj: field1,field2, Field3

方法

和对象一起绑定的函数,我们称之为对象的方法。方法其实就是函数,只不过是隐含传递了Self参数的函数。换句话说,方法的第一个参数总是Self,只不过对于对象的使用者来说,是看不见的,编译器将它隐藏了。

 

          procedure mymethod(self: TMyObj) = procedure TMyObj.mymethod;

注意:尽管在Object 的方法中能够使用Class关键字,但是遗憾的是,传递进入到方法的Self 参数依然如是,而不是Object VMT地址指针。

  这应该是Delphi的Bug.
  TMyChildObj = Object(TMyObj)
public
class procedure Test1; //but there no any difference with procedure Test1!!
end;

虚方法

不象静态方法,虚方法的执行地址不是在编译时期就能确定的,它是在运行时通过查虚方法地址表确定的。虚方法具体我就不多说了,讲的书很多。

要让一个方法成为虚方法,只要在方法声明后面添加 virtual 指令,即可,这样编译器将在VMT表中增加一个虚方法的入口地址项。

注意,和类类型不同的是,在它的后代重载虚方法的时候,不是用orverride 指令,而是用 virtual指令,这个是为了兼容以前的Turbo Pascal,其实我觉得已经没有必要了!!或者蛮可以同时支持的,也能兼容阿。

当你派生一个新的对象的时候,如果你为子类添加或重载了新的虚方法,那么该对象将会创建一个自己的VMT表而不是引用父类的VMT表!那个创建的子类的VMT包含了父类的VMT表的所有入口并附加上自己修改或添加的地址入口项。

 

  TMyObj = Object
protected
Field1: Integer;
field2: Integer;
public
constructor Create;
destructor Destroy;
procedure mymethod;virtual;
end;
TMyChildObj = Object(TMyObj)
protected
Field3: Integer;
public
procedure mymethod;virtual;
end;

注意:在调用虚方法之前,你必须调用 constructor Create 方法初始化,constructor 方法将会将正确的VMT入口地址填写到该记录上,其实对于静态对象,这个工作完全可以在编译时期作,只有动态对象(指针)才需要在运行期分配内存的时候填写VMT地址!

对于使用了虚方法的对象,该对象实例的记录内存会增加1个指针VMTPtr,也就是4个字节的大小,用来指向该对象的VMT入口地址。

对象变量的内存印象如下:

 

MyObj: Field1,Field2, VMTPtr
MyChildObj: Field1,Field2, VMTPtr, Field3

虚方法表的结构

偏移量 类型 说明
-12 Pointer 指向虚方法表的地址(也就是偏移量为 0的地址)
-8 Cardinal 该对象记录实例的大小(字节单位)
-4 Cardinal VMTPtr 字段在该对象记录实例中的偏移量
0 Pointer 指向第一个用户定义的虚方法入口地址

属性

对象类型支持属性,但是你不能将属性声明为Published 成员(因为Object 类型没有 RTTI )。

 

  TMyChildObj = Object(TMyObj)
protected
Field3: Integer;
FInt: Integer;
public
procedure mymethod;virtual;
property Int: Integer read FInt;
end;

注意:如果在你声明的对象中只有虚方法而没有任何字段请不要使用属性! 这应该是Delphi的一个Bug. 比如这样:

  TObj = Object
protected
function GetInt: Integer;
procedure VM;Virtual;
public
property Int: Integer read GetInt;
end;
这时候,传入
GetInt 方法的 self parameter 不是对象记录的地址,而不是该对象VMT 的地址。所以将有可能出现错误!

Class types

class 类型是动态分配内存的对象扩展。它是一个指向对象实例的指针。 Class 支持 RTTI(published), dynamic method and class-ref type. 为了添加这些功能 class 类型的VMT 被完全改变。所有的Class type 都有一个共同的祖先对象: TObject 类类型.

如果可以用对象类型来声明TObject类型,那么它就是该是类似这样:

  TObject = ^T_Object;
T_Object = Object
protected
{these are the pointer in the VMT, not virtual method!!}
function SelfPtr           : TClass;virtual;
function IntfTable         : PInterfaceTable;virtual;
function AutoTable         : Pointer;virtual;
function InitTable         : Pointer;virtual;
function TypeInfo          : PTypeInfo;virtual;
function FieldTable        : PFieldTable;virtual;
function MethodTable       : PPublishedMethodTable;virtual;
function DynamicTable      : PDynamicMethodTable;virtual;
function ClassName         : PShortString;virtual;
function InstanceSize      : PLongint;virtual;
function Parent            : PClass;virtual;
{these are the virtual methods}
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;

 

  TMyClassObj = Class
protected
Field1: Integer;
field2: Integer;
public
constructor Create;
destructor Destroy;override;
procedure mymethod;virtual;
end;
TMyChildClassObj = Class(TMyClassObj)
protected
Field3: Integer;
public
procedure mymethod;override;
end;

Virtual Method

对于Virtual Method 不用多说.每一个类都一张独立的VMT表,上面按顺序列出了每个虚方法的代码地址. 如果子类的Virtual方法没有重载,那么该子类的VMT上的虚方法地址和父类的虚方法地址是完全一样的。

虚方法表的结构

Offset Type Description
-76 Pointer pointer to virtual method table (or nil)
-72 Pointer pointer to interface table (or nil)
-68 Pointer pointer to Automation information table (or nil)
-64 Pointer pointer to instance initialization table (or nil)
-60 Pointer pointer to type information table (or nil)
-56 Pointer pointer to field definition table (or nil)
-52 Pointer pointer to method definition table (or nil)
-48 Pointer pointer to dynamic method table (or nil)
-44 Pointer pointer to short string containing class name
-40 Cardinal instance size in bytes
-36 Pointer pointer to a pointer to ancestor class (or nil)
-32 Pointer pointer to entry point of SafecallException method (or nil)
-28 Pointer entry point of AfterConstruction method
-24 Pointer entry point of BeforeDestruction method
-20 Pointer entry point of Dispatch method
-16 Pointer entry point of DefaultHandler method
-12 Pointer entry point of NewInstance method
-8 Pointer entry point of FreeInstance method
-4 Pointer entry point of Destroy destructor
0 Pointer entry point of first user-defined virtual method
4 Pointer entry point of second user-defined virtual method
... ... ...

Dynamic method

Virtual Method 以牺牲空间换速度,Dynamic method是以牺牲速度换取空间的节约。如果不是特别的原因(基类上有大量的虚方法,大量的派生类),建议你用Virtual Method 用以获取更好的时间性能。

消息方法就是动态方法。需要注意的是 Delphi.net 中的Dynamic method其实是Virtual method。在.net 中对于消息使用的是基于attribute and reflection 的解决方案。动态方法起始于16位Windows和DOS的年代,在那个时代由于受到64KB数据段的限制,不得不想法压缩内存空间。设想下如果你的一个基类有大量的虚方法,并且有大量基于它的派生类,这些派生类只重载了少量的虚方法,这无疑将浪费大量的VMT空间,这因为没有被重载的方法依然在派生类中的VMT上占有位置,用来指向基类的方法。而动态方法,就不是通过为每一个类建立这样的稀疏数组的方式了;仅仅只有新的动态方法或重载的动态方法才会出现在动态方法表(DMT)上面。

在Win32环境下没有了内存段空间限制(最大可以为2G),Delphi 32 将VMT存放在代码段了。

动态方法表(Dynamic method table)的地址在VMT中,一个类仅当它声明(或重载)了动态(包括消息)方法才会有动态方法表(DMT)。

动态方法表的结构

偏移量 类型 说明
Count Word 表明该类包含多少个动态方法。
Index array[0..Count-1] of Word 动态方法的索引号
Methods array[0..Count-1] of Pointer 动态方法的地址