C
cAkk
Unregistered / Unconfirmed
GUEST, unregistred user!
【Jie】 <!--(来自 202.110.171.129)--> 于 00-5-2 22:11:36 加贴在 <font color='navy'><strong>DELPHI技术</strong></font> <a href='list.asp?boardid=68' target=BoardList><font color=red><b>↑</b></font></a>:</p><p>
在使用DELPHI开发软件的过程中,我们就像草原上一群快乐牛羊,无忧无虑地
享受着Object Pascal语言为我们带来的阳光和各种VCL控件提供的丰富的水
草。抬头望望无边无际蔚蓝的天空,低头品尝大地上茂密的青草,谁会去想宇宙
有多大,比分子和原子更小的东西是什么?那是哲学家的事。而哲学家此时正坐
在高高的山顶上,仰望宇宙星云变换,凝视地上小虫的爬行,蓦然回头,对我们
这群吃草的牛羊点头微笑。随手扯起一根小草,轻轻地含在嘴里,闭上眼睛细细
品尝,不知道这根青草在哲学家的嘴里是什么味道?只是,他的脸上一直带着满
意的微笑。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;认识和了解DELPHI微观的原子世界,可以使我们
彻底理解DELPHI的宏观应用程序结构,从而在更广阔的思想空间中开发我们的
软件。这就好像,牛顿发现了宏观物体的运动,却因为搞不清物体为什么会这样
运动而苦恼,相反,爱因斯坦却在基本粒子规律和宏观物体运动之间体验着相对
论的快乐生活! <br>
<br>
第一节&nbsp;&nbsp;TObject原子 <br>
&nbsp;&nbsp;&nbsp;&nbsp;TObject是什么? <br>
&nbsp;&nbsp;&nbsp;&nbsp;是Object Pascal语言体系结构的基本核心,也是
各种VCL控件的起源。我们可以认为,TObject是构成DELPHI应用程序的原子之
一,当然,他们又是由基本Pascal语法元素等更细微的粒子构成。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;说TObject是DELPHI程序的原子,是因为TObject
是DELPHI编译器内部支持的。所有的对象类都是从TObject派生的,即使你并未
指定TObject为祖先类。TObject被定义在System单元,它是系统的一部分。在
System.pas单元的开头,有这样的注释文本: <br>
&nbsp;&nbsp;&nbsp;&nbsp;{ Predefined constants, types, procedures, } <br>
&nbsp;&nbsp;&nbsp;&nbsp;{ and functions (such as True, Integer, or } <br>
&nbsp;&nbsp;&nbsp;&nbsp;{ Writeln) do not have actual declarations.} <br>
&nbsp;&nbsp;&nbsp;&nbsp;{ Instead they are built into the compiler } <br>
&nbsp;&nbsp;&nbsp;&nbsp;{ and are treated as if they were declared } <br>
&nbsp;&nbsp;&nbsp;&nbsp;{ at the beginning of the System unit.&nbsp;&nbsp;&nbsp;&nbsp;} <br>
&nbsp;&nbsp;&nbsp;&nbsp;它的意思说,这一单元包含预定义的常量、类型、
过程和函数(诸如:Ture、Integer或Writeln),它们并没有实际的声明,而
是编译器内置的,并在编译的开始就被认为是已经声明的定义。你可以将
Classes.pas或Windows.pas等其他源程序文件加入你的项目文件中进行编译和
调试其源代码,但你绝对无法将System.pas源程序文件加入到你的项目文件中
进行编译!DELPHI将报告重复定义System的编译错误! <br>
&nbsp;&nbsp;&nbsp;&nbsp;因此,TObject是编译器内部提供的定义,对于我
们使用DELPHI开发程序的人来说,TObject是原子性的东西。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;TObject在System单元中的定义是这样的: <br>
&nbsp;&nbsp;TObject = class <br>
&nbsp;&nbsp;&nbsp;&nbsp;constructor Create
<br>
&nbsp;&nbsp;&nbsp;&nbsp;procedure Free
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function InitInstance(Instance: Pointer): TObject
<br>
&nbsp;&nbsp;&nbsp;&nbsp;procedure CleanupInstance
<br>
&nbsp;&nbsp;&nbsp;&nbsp;function ClassType: TClass
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function ClassName: ShortString
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function ClassNameIs(const Name: string): Boolean
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function ClassParent: TClass
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function ClassInfo: Pointer
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function InstanceSize: Longint
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function InheritsFrom(AClass: TClass): Boolean
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function MethodAddress(const Name: ShortString): Pointer
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function MethodName(Address: Pointer): ShortString
<br>
&nbsp;&nbsp;&nbsp;&nbsp;function FieldAddress(const Name: ShortString): Pointer
<br>
&nbsp;&nbsp;&nbsp;&nbsp;function GetInterface(const IID: TGUID
out Obj): Boolean
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function GetInterfaceEntry(const IID: TGUID): PInterfaceEntry
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function GetInterfaceTable: PInterfaceTable
<br>
&nbsp;&nbsp;&nbsp;&nbsp;function SafeCallException(ExceptObject: TObject
<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ExceptAddr: Pointer): HResult
virtual
<br>
&nbsp;&nbsp;&nbsp;&nbsp;procedure AfterConstruction
virtual
<br>
&nbsp;&nbsp;&nbsp;&nbsp;procedure BeforeDestruction
virtual
<br>
&nbsp;&nbsp;&nbsp;&nbsp;procedure Dispatch(var Message)
virtual
<br>
&nbsp;&nbsp;&nbsp;&nbsp;procedure DefaultHandler(var Message)
virtual
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function NewInstance: TObject
virtual
<br>
&nbsp;&nbsp;&nbsp;&nbsp;procedure FreeInstance
virtual
<br>
&nbsp;&nbsp;&nbsp;&nbsp;destructor Destroy
virtual
<br>
&nbsp;&nbsp;end
<br>
<br>
&nbsp;&nbsp;&nbsp;&nbsp;下面,我们将逐步敲开TObject原子的大门,看看里面到底是什么结构。 <br>
<br>
&nbsp;&nbsp;&nbsp;&nbsp;我们知道,TObject是所有对象的基本类,那么,一个对象到底是什么? <br>
&nbsp;&nbsp;DELPHI中的任何对象都是一个指针,这个指针指明该对象在内存
中所占据的一块空间!虽然,对象是一个指针,可是我们引用对象的成员时却不
用写成这样的代码MyObject^.GetName,而只能写成MyObject.GetName,这是
Object Pascal语言扩充的语法,是由编译器支持的。使用C++ Builder的朋友
就很清楚对象与指针的关系,因为在C++ Builder的对象都要定义为指针。对象
指针指向的地方就是对象存储数据的对象空间,我们来分析一下对象指针指向的
内存空间的数据结构。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;对象空间的头4个字节是指向该对象类的虚方法地
址表(VMT – Vritual Method Table)。接下来的空间就是存储对象本身成
员数据的空间,并按从该对象最原始祖先类的数据成员到该对象类的数据成员的
总顺序,和每一级类中数据成员的定义顺序存储。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;类的虚方法地址表(VMT)保存从该类的原始祖先
类派生到该类的所有类的虚方法的过程地址。类的虚方法,就是用保留字
vritual声明的方法,虚方法是实现对象多态性的基本机制。虽然,用保留字
dynamic声明的动态方法也可实现对象的多态性,但这样的方法不保存在虚方法
地址表(VMT)中,它只是Object Pascal提供的另一种可节约类存储空间的多
态实现机制,但却是以牺牲调用速度为代价的。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;即使,我们自己并未定义任何类的虚方法,但该类
的对象仍然存在指向虚方法地址表的指针,只是地址项的长度为零。可是,在
TObject中定义的那些虚方法,如Destroy、FreeInstance等等,又存储在什么
地方呢?原来,他们的方法地址存储在相对VMT指针负方向偏移的空间中。其
实,在VMT表的负方向偏移76个字节的数据空间是对象类的系统数据结构,这些
数据结构是与编译器相关的,并且在将来的DELPHI版本中有可能被改变。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;因此,你可以认为,VMT是一个从负偏移地址空间
开始的数据结构,负偏移数据区是VMT的系统数据区,VMT的正偏移数据是用户
数据区(自定义的虚方法地址表)。TObject中定义的有关类信息或对象运行时
刻信息的函数和过程,一般都与VMT的系统数据有关。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;一个VMT数据就代表一个类,其实VMT就是类!在
Object Pascal中我们用TObject、TComponent等等标识符表示类,它们在
DELPHI的内部实现为各自的VMT数据。而用class of保留字定义的类的类型,
实际就是指向相关VMT数据的指针。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;对我们的应用程序来说,VMT数据是静态的数据,
当编译器编译完成我们的应用程序之后,这些数据信息已经确定并已初始化。我
们编写的程序语句可访问VMT相关的信息,获得诸如对象的尺寸、类名或运行时
刻的属性资料等等信息,或者调用虚方法或读取方法的名称与地址等等操作。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;当一个对象产生时,系统会为该对象分配一块内存
空间,并将该对象与相关的类联系起来,于是,在为对象分配的数据空间中的头
4个字节,就成为指向类VMT数据的指针。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;我们再来看看对象是怎样诞生和灭亡的。看着我三
岁的儿子在草地上活蹦乱跳,正是由于亲眼目睹过生命的诞生过程,我才能真真
体会到生命的意义和伟大。也只有那些经历过死别的人,才会更加理解和珍惜生
命。那么,就让我们理解一下对象的产生和消亡的过程吧! <br>
&nbsp;&nbsp;&nbsp;&nbsp;我们都知道,用下面的语句可以构造一个最简单对象: <br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AnObject := TObject.Create
<br>
&nbsp;&nbsp;&nbsp;&nbsp;编译器将其编译实现为: <br>
&nbsp;&nbsp;&nbsp;&nbsp;用TObject对应的VMT为依据,调用TObject的
Create构造函数。而在Create构造函数调用了系统的ClassCreate过程,系统
的ClassCreate过程又通过存储在类VMT调用NewInstance虚方法。调用
NewInstance方法的目的是要建立对象的实例空间,因为我们没有重载该方法,
所以,它就是TObject类的NewInstance。TObjec类的NewInstance方法将根据
编译器在VMT表中初始化的对象实例尺寸(InstanceSize),调用GetMem过程
为该对象分配内存,然后调用InitInstance方法将分配的空间初始化。
InitInstance方法首先将对象空间的头4个字节初始化为指向对象类对应VMT的
指针,然后将其余的空间清零。建立对象实例之后,还调用了一个虚方法
AfterConstruction。最后,将对象实例数据的地址指针保存到AnObject变量
中,这样,AnObject对象就诞生了。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;同样,用下面的语句可以消灭一个对象: <br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AnObject.Destroy
<br>
&nbsp;&nbsp;&nbsp;&nbsp;TObject的析构函数Destroy被声明为虚方法,它
也是系统固有的虚方法之一。Destory方法首先调用了BeforeDestruction虚方
法,然后调用系统的ClassDestroy过程。ClassDestory过程又通过类VMT调用
FreeInstance虚方法,由FreeInstance方法调用FreeMem过程释放对象的内存
空间。就这样,一个对象就在系统中消失。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;对象的析构过程比对象的构造过程简单,就好像生
命的诞生是一个漫长的孕育过程,而死亡却相对的短暂,这似乎是一种必然的规律。 <br>
在对象的构造和析构过程中,调用了NewInstance和FreeInstance两个虚函
数,来创建和释放对象实例的内存空间。之所以将这两个函数声明为虚函数,是
为了能让用户在编写需要用户自己管理内存的特殊对象类时(如在一些特殊的工
业控制程序中),有扩展的空间。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;而将AfterConstruction和BeforeDestruction
声明为虚函数,也是为了将来派生的类在产生对象之后,有机会让新诞生的对象
呼吸第一口新鲜空气,而在对象消亡之前可以允许对象完成善后事宜,这都是合
情合理的事。其实,TForm对象和TDataModule对象的OnCreate事件和
OnDestroy事件,就是在TForm和TDataModule重载的这两个虚函数过程分别触发的。 <br>
此外,TObjec还提供了一个Free方法,它不是虚方法,它是为了那些搞不清对
象是否为空(nil)的情况下能安全释放对象而专门提供的。其实,搞不清对象
是否为空,本身就有程序逻辑不清晰的问题。不过,任何人都不是完美的,都可
能犯错,使用Free能避免偶然的错误也是件好事。然而,编写正确的程序不能
一味依靠这样的解决方法,还是应该以保证程序的逻辑正确性为编程的第一目标! <br>
&nbsp;&nbsp;&nbsp;&nbsp;有兴趣的朋友可以读一读System单元的原代码,
其中,大量的代码是用汇编语言书写的。细心的朋友可以发现,TObject的构造
函数Create和析构函数Destory竟然没有写任何代码,其实,在调试状态下通过
Debug的CPU窗口,可清楚地反映出Create和Destory的汇编代码。因为,缔造
DELPHI的大师门不想将过多复杂的东西提供给用户,他们希望用户在简单的概
念上编写应用程序,将复杂的工作隐藏在系统的内部由他们承担。所以,在发布
System.pas单元时特别将这两个函数的代码去掉,让用户认为TObject是万物
之源,用户派生的类完全从虚无中开始,这本身并没有错。虽然,阅读DELPHI
的这些最本质的代码需要少量的汇编语言知识,但阅读这样的代码,可以让我们
更深刻认识DELPHI世界的起源和发展的基本规律。即使看不太懂,能起码了解
一些基本东西,对我们编写DELPHI程序也是大有帮助。 <br>
<br>
第二节&nbsp;&nbsp;TClass原子 <br>
&nbsp;&nbsp;&nbsp;&nbsp;在System.pas单元中,TClass是这样定义的: <br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;TClass = class of TObject
<br>
&nbsp;&nbsp;&nbsp;&nbsp;它的意思是说,TClass是TObject的类。因为
TObject本身就是一个类,所以TClass就是所谓的类的类。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;从概念上说,TClass是类的类型,即,类之类。
但是,我们知道DELPHI的一个类,代表着一项VMT数据。因此,类之类可以认为
是为VMT数据项定义的类型,其实,它就是一个指向VMT数据的指针类型! <br>
在以前传统的C++语言中,是不能定义类的类型的。对象一旦编译就固定下来,
类的结构信息已经转化为绝对的机器代码,在内存中将不存在完整的类信息。一
些较高级的面向对象语言才可支持对类信息的动态访问和调用,但往往需要一套
复杂的内部解释机制和较多的系统资源。而DELPHI的Object Pascal语言吸收
了一些高级面向对象语言的优秀特征,又保留可将程序直接编译成机器代码的传
统优点,比较完美地解决了高级功能与程序效率的问题。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;正是由于DELPHI在应用程序中保留了完整的类信
息,才能提供诸如as和is等在运行时刻转换和判别类的高级面向对象功能,而
类的VMT数据在其中起了关键性的核心作用。有兴趣的朋友可以读一读System单
元的AsClass和IsClass两个汇编过程,他们是as和is操作符的实现代码,以加
深对类和VMT数据的理解。 <br>
<br>
&nbsp;&nbsp;&nbsp;&nbsp;...... <br>
&nbsp;&nbsp;&nbsp;&nbsp;后面的内容还有对虚构造函数的理解,Interface
的实现机制和异常处理的实现机制,等等DLPHI的基本原理。希望五一之后能写
完,再贡献给大家。&nbsp;</p>
在使用DELPHI开发软件的过程中,我们就像草原上一群快乐牛羊,无忧无虑地
享受着Object Pascal语言为我们带来的阳光和各种VCL控件提供的丰富的水
草。抬头望望无边无际蔚蓝的天空,低头品尝大地上茂密的青草,谁会去想宇宙
有多大,比分子和原子更小的东西是什么?那是哲学家的事。而哲学家此时正坐
在高高的山顶上,仰望宇宙星云变换,凝视地上小虫的爬行,蓦然回头,对我们
这群吃草的牛羊点头微笑。随手扯起一根小草,轻轻地含在嘴里,闭上眼睛细细
品尝,不知道这根青草在哲学家的嘴里是什么味道?只是,他的脸上一直带着满
意的微笑。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;认识和了解DELPHI微观的原子世界,可以使我们
彻底理解DELPHI的宏观应用程序结构,从而在更广阔的思想空间中开发我们的
软件。这就好像,牛顿发现了宏观物体的运动,却因为搞不清物体为什么会这样
运动而苦恼,相反,爱因斯坦却在基本粒子规律和宏观物体运动之间体验着相对
论的快乐生活! <br>
<br>
第一节&nbsp;&nbsp;TObject原子 <br>
&nbsp;&nbsp;&nbsp;&nbsp;TObject是什么? <br>
&nbsp;&nbsp;&nbsp;&nbsp;是Object Pascal语言体系结构的基本核心,也是
各种VCL控件的起源。我们可以认为,TObject是构成DELPHI应用程序的原子之
一,当然,他们又是由基本Pascal语法元素等更细微的粒子构成。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;说TObject是DELPHI程序的原子,是因为TObject
是DELPHI编译器内部支持的。所有的对象类都是从TObject派生的,即使你并未
指定TObject为祖先类。TObject被定义在System单元,它是系统的一部分。在
System.pas单元的开头,有这样的注释文本: <br>
&nbsp;&nbsp;&nbsp;&nbsp;{ Predefined constants, types, procedures, } <br>
&nbsp;&nbsp;&nbsp;&nbsp;{ and functions (such as True, Integer, or } <br>
&nbsp;&nbsp;&nbsp;&nbsp;{ Writeln) do not have actual declarations.} <br>
&nbsp;&nbsp;&nbsp;&nbsp;{ Instead they are built into the compiler } <br>
&nbsp;&nbsp;&nbsp;&nbsp;{ and are treated as if they were declared } <br>
&nbsp;&nbsp;&nbsp;&nbsp;{ at the beginning of the System unit.&nbsp;&nbsp;&nbsp;&nbsp;} <br>
&nbsp;&nbsp;&nbsp;&nbsp;它的意思说,这一单元包含预定义的常量、类型、
过程和函数(诸如:Ture、Integer或Writeln),它们并没有实际的声明,而
是编译器内置的,并在编译的开始就被认为是已经声明的定义。你可以将
Classes.pas或Windows.pas等其他源程序文件加入你的项目文件中进行编译和
调试其源代码,但你绝对无法将System.pas源程序文件加入到你的项目文件中
进行编译!DELPHI将报告重复定义System的编译错误! <br>
&nbsp;&nbsp;&nbsp;&nbsp;因此,TObject是编译器内部提供的定义,对于我
们使用DELPHI开发程序的人来说,TObject是原子性的东西。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;TObject在System单元中的定义是这样的: <br>
&nbsp;&nbsp;TObject = class <br>
&nbsp;&nbsp;&nbsp;&nbsp;constructor Create
<br>
&nbsp;&nbsp;&nbsp;&nbsp;procedure Free
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function InitInstance(Instance: Pointer): TObject
<br>
&nbsp;&nbsp;&nbsp;&nbsp;procedure CleanupInstance
<br>
&nbsp;&nbsp;&nbsp;&nbsp;function ClassType: TClass
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function ClassName: ShortString
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function ClassNameIs(const Name: string): Boolean
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function ClassParent: TClass
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function ClassInfo: Pointer
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function InstanceSize: Longint
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function InheritsFrom(AClass: TClass): Boolean
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function MethodAddress(const Name: ShortString): Pointer
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function MethodName(Address: Pointer): ShortString
<br>
&nbsp;&nbsp;&nbsp;&nbsp;function FieldAddress(const Name: ShortString): Pointer
<br>
&nbsp;&nbsp;&nbsp;&nbsp;function GetInterface(const IID: TGUID
out Obj): Boolean
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function GetInterfaceEntry(const IID: TGUID): PInterfaceEntry
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function GetInterfaceTable: PInterfaceTable
<br>
&nbsp;&nbsp;&nbsp;&nbsp;function SafeCallException(ExceptObject: TObject
<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ExceptAddr: Pointer): HResult
virtual
<br>
&nbsp;&nbsp;&nbsp;&nbsp;procedure AfterConstruction
virtual
<br>
&nbsp;&nbsp;&nbsp;&nbsp;procedure BeforeDestruction
virtual
<br>
&nbsp;&nbsp;&nbsp;&nbsp;procedure Dispatch(var Message)
virtual
<br>
&nbsp;&nbsp;&nbsp;&nbsp;procedure DefaultHandler(var Message)
virtual
<br>
&nbsp;&nbsp;&nbsp;&nbsp;class function NewInstance: TObject
virtual
<br>
&nbsp;&nbsp;&nbsp;&nbsp;procedure FreeInstance
virtual
<br>
&nbsp;&nbsp;&nbsp;&nbsp;destructor Destroy
virtual
<br>
&nbsp;&nbsp;end
<br>
<br>
&nbsp;&nbsp;&nbsp;&nbsp;下面,我们将逐步敲开TObject原子的大门,看看里面到底是什么结构。 <br>
<br>
&nbsp;&nbsp;&nbsp;&nbsp;我们知道,TObject是所有对象的基本类,那么,一个对象到底是什么? <br>
&nbsp;&nbsp;DELPHI中的任何对象都是一个指针,这个指针指明该对象在内存
中所占据的一块空间!虽然,对象是一个指针,可是我们引用对象的成员时却不
用写成这样的代码MyObject^.GetName,而只能写成MyObject.GetName,这是
Object Pascal语言扩充的语法,是由编译器支持的。使用C++ Builder的朋友
就很清楚对象与指针的关系,因为在C++ Builder的对象都要定义为指针。对象
指针指向的地方就是对象存储数据的对象空间,我们来分析一下对象指针指向的
内存空间的数据结构。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;对象空间的头4个字节是指向该对象类的虚方法地
址表(VMT – Vritual Method Table)。接下来的空间就是存储对象本身成
员数据的空间,并按从该对象最原始祖先类的数据成员到该对象类的数据成员的
总顺序,和每一级类中数据成员的定义顺序存储。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;类的虚方法地址表(VMT)保存从该类的原始祖先
类派生到该类的所有类的虚方法的过程地址。类的虚方法,就是用保留字
vritual声明的方法,虚方法是实现对象多态性的基本机制。虽然,用保留字
dynamic声明的动态方法也可实现对象的多态性,但这样的方法不保存在虚方法
地址表(VMT)中,它只是Object Pascal提供的另一种可节约类存储空间的多
态实现机制,但却是以牺牲调用速度为代价的。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;即使,我们自己并未定义任何类的虚方法,但该类
的对象仍然存在指向虚方法地址表的指针,只是地址项的长度为零。可是,在
TObject中定义的那些虚方法,如Destroy、FreeInstance等等,又存储在什么
地方呢?原来,他们的方法地址存储在相对VMT指针负方向偏移的空间中。其
实,在VMT表的负方向偏移76个字节的数据空间是对象类的系统数据结构,这些
数据结构是与编译器相关的,并且在将来的DELPHI版本中有可能被改变。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;因此,你可以认为,VMT是一个从负偏移地址空间
开始的数据结构,负偏移数据区是VMT的系统数据区,VMT的正偏移数据是用户
数据区(自定义的虚方法地址表)。TObject中定义的有关类信息或对象运行时
刻信息的函数和过程,一般都与VMT的系统数据有关。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;一个VMT数据就代表一个类,其实VMT就是类!在
Object Pascal中我们用TObject、TComponent等等标识符表示类,它们在
DELPHI的内部实现为各自的VMT数据。而用class of保留字定义的类的类型,
实际就是指向相关VMT数据的指针。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;对我们的应用程序来说,VMT数据是静态的数据,
当编译器编译完成我们的应用程序之后,这些数据信息已经确定并已初始化。我
们编写的程序语句可访问VMT相关的信息,获得诸如对象的尺寸、类名或运行时
刻的属性资料等等信息,或者调用虚方法或读取方法的名称与地址等等操作。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;当一个对象产生时,系统会为该对象分配一块内存
空间,并将该对象与相关的类联系起来,于是,在为对象分配的数据空间中的头
4个字节,就成为指向类VMT数据的指针。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;我们再来看看对象是怎样诞生和灭亡的。看着我三
岁的儿子在草地上活蹦乱跳,正是由于亲眼目睹过生命的诞生过程,我才能真真
体会到生命的意义和伟大。也只有那些经历过死别的人,才会更加理解和珍惜生
命。那么,就让我们理解一下对象的产生和消亡的过程吧! <br>
&nbsp;&nbsp;&nbsp;&nbsp;我们都知道,用下面的语句可以构造一个最简单对象: <br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AnObject := TObject.Create
<br>
&nbsp;&nbsp;&nbsp;&nbsp;编译器将其编译实现为: <br>
&nbsp;&nbsp;&nbsp;&nbsp;用TObject对应的VMT为依据,调用TObject的
Create构造函数。而在Create构造函数调用了系统的ClassCreate过程,系统
的ClassCreate过程又通过存储在类VMT调用NewInstance虚方法。调用
NewInstance方法的目的是要建立对象的实例空间,因为我们没有重载该方法,
所以,它就是TObject类的NewInstance。TObjec类的NewInstance方法将根据
编译器在VMT表中初始化的对象实例尺寸(InstanceSize),调用GetMem过程
为该对象分配内存,然后调用InitInstance方法将分配的空间初始化。
InitInstance方法首先将对象空间的头4个字节初始化为指向对象类对应VMT的
指针,然后将其余的空间清零。建立对象实例之后,还调用了一个虚方法
AfterConstruction。最后,将对象实例数据的地址指针保存到AnObject变量
中,这样,AnObject对象就诞生了。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;同样,用下面的语句可以消灭一个对象: <br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AnObject.Destroy
<br>
&nbsp;&nbsp;&nbsp;&nbsp;TObject的析构函数Destroy被声明为虚方法,它
也是系统固有的虚方法之一。Destory方法首先调用了BeforeDestruction虚方
法,然后调用系统的ClassDestroy过程。ClassDestory过程又通过类VMT调用
FreeInstance虚方法,由FreeInstance方法调用FreeMem过程释放对象的内存
空间。就这样,一个对象就在系统中消失。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;对象的析构过程比对象的构造过程简单,就好像生
命的诞生是一个漫长的孕育过程,而死亡却相对的短暂,这似乎是一种必然的规律。 <br>
在对象的构造和析构过程中,调用了NewInstance和FreeInstance两个虚函
数,来创建和释放对象实例的内存空间。之所以将这两个函数声明为虚函数,是
为了能让用户在编写需要用户自己管理内存的特殊对象类时(如在一些特殊的工
业控制程序中),有扩展的空间。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;而将AfterConstruction和BeforeDestruction
声明为虚函数,也是为了将来派生的类在产生对象之后,有机会让新诞生的对象
呼吸第一口新鲜空气,而在对象消亡之前可以允许对象完成善后事宜,这都是合
情合理的事。其实,TForm对象和TDataModule对象的OnCreate事件和
OnDestroy事件,就是在TForm和TDataModule重载的这两个虚函数过程分别触发的。 <br>
此外,TObjec还提供了一个Free方法,它不是虚方法,它是为了那些搞不清对
象是否为空(nil)的情况下能安全释放对象而专门提供的。其实,搞不清对象
是否为空,本身就有程序逻辑不清晰的问题。不过,任何人都不是完美的,都可
能犯错,使用Free能避免偶然的错误也是件好事。然而,编写正确的程序不能
一味依靠这样的解决方法,还是应该以保证程序的逻辑正确性为编程的第一目标! <br>
&nbsp;&nbsp;&nbsp;&nbsp;有兴趣的朋友可以读一读System单元的原代码,
其中,大量的代码是用汇编语言书写的。细心的朋友可以发现,TObject的构造
函数Create和析构函数Destory竟然没有写任何代码,其实,在调试状态下通过
Debug的CPU窗口,可清楚地反映出Create和Destory的汇编代码。因为,缔造
DELPHI的大师门不想将过多复杂的东西提供给用户,他们希望用户在简单的概
念上编写应用程序,将复杂的工作隐藏在系统的内部由他们承担。所以,在发布
System.pas单元时特别将这两个函数的代码去掉,让用户认为TObject是万物
之源,用户派生的类完全从虚无中开始,这本身并没有错。虽然,阅读DELPHI
的这些最本质的代码需要少量的汇编语言知识,但阅读这样的代码,可以让我们
更深刻认识DELPHI世界的起源和发展的基本规律。即使看不太懂,能起码了解
一些基本东西,对我们编写DELPHI程序也是大有帮助。 <br>
<br>
第二节&nbsp;&nbsp;TClass原子 <br>
&nbsp;&nbsp;&nbsp;&nbsp;在System.pas单元中,TClass是这样定义的: <br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;TClass = class of TObject
<br>
&nbsp;&nbsp;&nbsp;&nbsp;它的意思是说,TClass是TObject的类。因为
TObject本身就是一个类,所以TClass就是所谓的类的类。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;从概念上说,TClass是类的类型,即,类之类。
但是,我们知道DELPHI的一个类,代表着一项VMT数据。因此,类之类可以认为
是为VMT数据项定义的类型,其实,它就是一个指向VMT数据的指针类型! <br>
在以前传统的C++语言中,是不能定义类的类型的。对象一旦编译就固定下来,
类的结构信息已经转化为绝对的机器代码,在内存中将不存在完整的类信息。一
些较高级的面向对象语言才可支持对类信息的动态访问和调用,但往往需要一套
复杂的内部解释机制和较多的系统资源。而DELPHI的Object Pascal语言吸收
了一些高级面向对象语言的优秀特征,又保留可将程序直接编译成机器代码的传
统优点,比较完美地解决了高级功能与程序效率的问题。 <br>
&nbsp;&nbsp;&nbsp;&nbsp;正是由于DELPHI在应用程序中保留了完整的类信
息,才能提供诸如as和is等在运行时刻转换和判别类的高级面向对象功能,而
类的VMT数据在其中起了关键性的核心作用。有兴趣的朋友可以读一读System单
元的AsClass和IsClass两个汇编过程,他们是as和is操作符的实现代码,以加
深对类和VMT数据的理解。 <br>
<br>
&nbsp;&nbsp;&nbsp;&nbsp;...... <br>
&nbsp;&nbsp;&nbsp;&nbsp;后面的内容还有对虚构造函数的理解,Interface
的实现机制和异常处理的实现机制,等等DLPHI的基本原理。希望五一之后能写
完,再贡献给大家。&nbsp;</p>