码农的笔记

Delphi虽好,但已不流行; 博客真好,可以做笔记

博客园 首页 新随笔 联系 订阅 管理

多态性的概念:
很通俗地讲,多态性是指可以将子对象赋值给父对象的技术,可以通过父对象调用子对象从父对
象继承并覆盖了的虚方法。或者说,多态性是可以让父对象具有不同行动方式的技术。
注意上面一句话包含了两个特性:
(1)可以将子对象赋值给父对象的技术。
(2)可以通过父对象调用子对象从父对象继承并覆盖了的虚方法。通过父对象调用子对象的虚方
法时,执行的是子对象覆盖后的代码,而不是父对象实现该虚方法的代码。

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

数据相关 字样不是真的只数据,而是域(Field)、方法地址表...........

对象中的相关数据有域(Field),可能有接口地址表、....

 

 

这幅图只供理解参考,与真实的还是有偏差

 

这个连接可能有一点用(Delphi中对象和类空间虚拟方法表之一

 

 

 

 

Var 

  D:TD;//D定义为TD类的变量;

 

此图仅供参考,可能理解不对!

 

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

 c++语言虚函数实现多态的原理(更新版) - coding小菜鸟 - 博客园  https://www.cnblogs.com/xgmzhna/p/10934562.html

 

---------

初学Delphi的可以多去万一博客,想提高Delphi的也可以看万一博客

万一博客中有用到多态:关于类的入门的例子(5): override - 万一 - 博客园  https://www.cnblogs.com/del/archive/2007/12/17/1002959.html

 -------

 

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

请参考:Delphi 类与对象内存结构浅析(上)_starsky2006的专栏-CSDN博客  https://blog.csdn.net/starsky2006/article/details/5497082

             Delphi 类与对象内存结构浅析(下)_starsky2006的专栏-CSDN博客  https://blog.csdn.net/starsky2006/article/details/5497113

 

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

我们知道,TObject 是所有类的基本类,也就是说,所有类都直接或者间接派生于TObject。尽管
在Delphi 中可以定义不从TObject 派生的类,但是这样的方式已经很少使用了,我们不再讨论它。因
此,TObject 是非常重要的。它可是VCL 的命根子。
我们知道,一个TObject 的实例:Object(对象),实际上是一个4 字节的指针,该指针指向对象
的实际数据区(Object Data)。那么这个“对象数据区”到底是怎么回事呢?对象的字段、方法、属性、
事件这些对象数据在“对象数据区”中究竟怎么组织、如何存取呢?
这的确是设计和实现VCL 架构的一个根本性问题,是命根子的命根子。
原来,这个对象数据区是划分为很多个小区域的。这些区域分为两个部分:
(1)头4 个字节存放一个指针,该指针指向另一个地址区域。
(2)其余小区域分别存储对象的各种数据成员(即字段,不包括方法。方法的入口地址被定义在
另一个内存表里,虚拟方法表中有一个指针指向该表)。
头4 个字节的指针指向的另一个地址区域是干嘛的呢?它就是我们常常听人说起的大名鼎鼎的
“虚拟方法表(Virtual Method Table,VMT)”!虚拟方法表又被划分为很多个大小为4 字节的小区域,
每个区域存放一个指针,每个指针对应一个虚拟方法的入口地址。
接下来的众多小区域用来存放字段、属性值和所有非虚方法(注意这里用的是“非虚方法”而不
是“非虚拟方法”,本节后面要详细讲述其中来由)的入口地址。
由以上可见,非虚方法成员的存取是相对简单的,而虚方法的寻址和调用则相对复杂得多。所以,
在本节中,我们重点要搞清楚的是虚拟方法表——VMT。

 

-------------------------

1. VMT 的结构图
仔细观察图5-2 可以看到:一个对象(Object)指针指向一个对象数据区(Object Data);对象数
据区的头4 个字节存放了一个指针,该指针再指向虚拟方法表(VMT)。

 

 


图5-2 VMT 结构图
一个虚拟方法表从指针所指地址的负偏移–76 处开始,长度动态分配(由虚拟方法的个数确定)。
虚拟方法表被分为很多小段,每段占据4 个字节,也就是众多指针。每个指针指向一个虚拟方法的入
口地址。
很显然,有了一个对象指针,按图索骥,是一定可能寻址到该对象一个指定的虚拟方法,从而调
用执行这个虚拟方法。
上面的分析很对人胃口,一下子就让人明白了VMT 是个什么东西。不过要彻底弄清VMT,它却
又露出另外一张面孔,可见,实际上并不是那么简单的。所以我还想说的是:
(1)VMT 还可以细分为两个区域,即基础信息区和用户定义虚拟方法区。
(2)VMT 对应于类而不是对象。
VMT 的负偏移区(–76~0)是基础信息区,存储了基础性数据(如实例大小)、基础性数据(如
接口表、运行时类型信息表、字段表、方法表、类名和父类虚拟方法表等)的指针和所有基础性虚拟
方法(这些虚拟方法都是在类TObject 中定义的,如AfterConstruction、BeforeDestruction、DefaultHandler、
Destroy 等)的指针,所以基础信息区并不全是指针列表。这个区域所存放的数据和指针主要是用来帮
助实现对象的构造和析构、运行时类型信息存取、字段和方法解析等。基础信息区的大小是固定的。
正偏移区(从0 开始)是用户定义的虚拟方法(即所有非TObject 定义的虚拟方法)所在区域,
每4 个字节存储一个用户定义的虚拟方法指针。这些虚拟方法不光是指在本类定义的,还包括从
TObject 一直到本类的所有中间类定义的所有虚拟方法。因此,这个区的大小是根据虚拟方法的个数决定的,不像基础信息区有固定的值。

不同的类总是具有独立的VMT,即使这些类有继承关系或者非常近似。也就是说,VMT 是根据
类的定义来生成的,跟类的实例没有关系。TComponent 和TControl 因为是两个不同的类,因此,它
们的VMT 也是毫不相关的。
在前面章节中我们提到了类引用类型(Class reference)。一个对象的类引用可以通过调用System
单元实现的TObject.ClassType 方法来获得:

 

可见,类引用实际上就是指向VMT 的指针。也就是说,类引用和VMT 有惟一对应关系。
Delphi 中的VMT 和C++、COM 中的VMT 是兼容的,具有类似的结构。在将来版本的Delphi 中,
VMT 的结构可能会作一定的调整,所以一般不要直接通过地址操作VMT,而代之以类和对象的方法、
属性来存取。
2. VMT 的产生
VMT 是由编译器为程序中每个要用到的类自动生成的。VMT 对应相应的类而不是类实例。即是
说,在没有创建一个类的任何实例时,该类的VMT 已经由编译器生成了,就等着一些指针去指向它。
如果一个类TParent 定义了一个虚拟方法F,并且实现了部分功能;然后有一个类TChild 派生于
TParent,并覆盖了虚拟方法F,做了附加功能实现。这时候编译器如何编译这个方法TChild.F,并添
加到VMT 中间呢?是不是要编译出两个方法即TParent.F 和TChild.F,生成两个指针加入VMT 呢?
不是的,编译器会将两个方法合成一个F,最终只产生一个指针并加入VMT。也就是说,TParent 和
TChild 各自的VMT 都有F 的指针,互不相关。
在调用构造函数创建一个对象时,对象数据区(Object Data)的头4 个字节被分配给一个指针,
然后,该指针被定向到在构造对象前就已经生成的VMT 上。从而赋予对象调用虚拟方法的能力。
3. virtual 方法和dynamic 方法的区别
在“类和类成员”一节中,我们讲了有两类虚方法:虚拟方法(virtual)和动态方法(dynamic)。
它们在功能上没有区别,都是为了实现子类覆盖。区别是在调用流程上。
前面说了“非虚方法”和“非虚拟方法”是有区别的,原因就是其中还夹了个动态方法的问题。
在上面的内容中,我们对动态方法的管理和调用只字未提,只是为了避免同时端出太多话题,从
而偏离主题,影响我们对VMT 的总体理解。不是有所谓“话分多头,各表一枝”嘛!
从上述VMT 的知识可以知道,virtual 方法被全部列入了VMT 的正偏移区,当一个对象请求调用
virtual 方法时,可以在类的VMT 中直接寻址,然后马上调用。但是调用一个dynamic 方法就没有这么
便宜了。

 

动态方法和VMT 负偏移的基础信息数据区有关系,请看下面:
在VMT 负偏移-48 处,是这么定义的:
-48 Pointer pointer to dynamic method table (or nil)(指针指向动态方法表(DMT))
可见,对于一个类,它用另外一个DMT(即动态方法表)来存储动态方法的入口地址。这是虚拟
方法和动态方法寻址和调用的一个重大区别。而DMT 又是依赖于VMT 来实现的。
DMT 是一系列的指针列表,和VMT 的正偏移区类似,存放了本类定义的和从父类继承并覆盖后
的动态方法入口地址。注意,如果TChild 并没有覆盖TParent.F,那么F 是不会存放到DMT 的。
因而,虚拟方法和动态方法相比,使用动态方法可以节省内存,因为它不存放未曾覆盖的虚方法
指针。而且调用一个虚拟方法和调用在DMT 中存放了入口地址的动态方法相比,速度也没有显著的
差异(不过是多了一个DMT 寻址而已)。而如果使用虚拟方法,即使TChild 并没有覆盖它,TParent
和TChild 的VMT 也都存放有F 的入口地址。
但是如果要调用DMT 没有入口地址的动态方法时,就麻烦了,这时候需要到父类甚至祖父类的
DMT 去寻址,才能最终完成一次调用,所以速度大打折扣。
可见,虚拟方法追求的是速度,而动态方法节省的是空间。一个是以空间换取时间,一个是以时
间换取空间,所谓有得必有失,而处理好了,有所失自然也有所得。
Delphi 之所以提供dynamic 这样一种虚方法实现机制,是为了在下面一种情况下实现VMT 内存
节省:一个父类声明了大量虚方法,并需要派生大量子类但子类很少覆盖父类虚方法时。这时候,如
果采用virtual,那么每个子类的VMT 都包含这个方法指针,显然在内存开销上划不来;而如果采用
dynamic 则节省多了。

DMT 机制是Object Pascal 特有的性质,因此和C++、COM 中的VMT 并不兼容,所以开
发跨语言使用的功能时则应该采用virtual 实现虚方法,牺牲一点空间

 

虚拟方法表(VMT)的基础信息区
偏移量/Byte 数据类型 内容
-76  Pointer 虚拟方法表本身地址
-72  Pointer 接口方法表地址
-68 Pointer 自动化信息表地址
-64 Pointer 类实例初始化信息表地址
-60 Pointer 运行时类型信息表地址
-56 Pointer 字段定义表地址
-52 Pointer 方法定义表地址
-48 Pointer 动态方法表地址
-44 Pointer 类名(ShortString 类型)地址
-40 Cardinal 类实例大小
-36 Pointer 父类虚拟方法表地址
-32 Pointer 虚拟方法TObject.SafecallException 入口
-28 Pointer 虚拟方法TObject.AfterConstruction 入口
-24 Pointer 虚拟方法TObject.BeforeDestruction 入口
-20 Pointer 虚拟方法TObject.Dispatch 入口
-16 Pointer 虚拟方法TObject.DefaultHandler 入口
-12 Pointer 虚拟方法TObject.NewInstance 入口
-8 Pointer 虚拟方法TObject.FreeInstance 入口
-4 Pointer 虚拟方法TObject.Destroy 入口

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 1 //Delphi7中System单元的代码
 2 
 3 { Virtual method table entries }
 4 
 5   vmtSelfPtr           = -76;
 6   vmtIntfTable         = -72;
 7   vmtAutoTable         = -68;
 8   vmtInitTable         = -64;
 9   vmtTypeInfo          = -60;
10   vmtFieldTable        = -56;
11   vmtMethodTable       = -52;
12   vmtDynamicTable      = -48;
13   vmtClassName         = -44;
14   vmtInstanceSize      = -40;
15   vmtParent            = -36;
16   vmtSafeCallException = -32 deprecated;  // don't use these constants.
17   vmtAfterConstruction = -28 deprecated;  // use VMTOFFSET in asm code instead
18   vmtBeforeDestruction = -24 deprecated;
19   vmtDispatch          = -20 deprecated;
20   vmtDefaultHandler    = -16 deprecated;
21   vmtNewInstance       = -12 deprecated;
22   vmtFreeInstance      = -8 deprecated;
23   vmtDestroy           = -4 deprecated;
24 
25   vmtQueryInterface    = 0 deprecated;
26   vmtAddRef            = 4 deprecated;
27   vmtRelease           = 8 deprecated;
28   vmtCreateObject      = 12 deprecated;

 

posted on 2021-06-18 12:00  码农的笔记  阅读(339)  评论(0编辑  收藏  举报