访问祖先类的虚方法

问题提出

在子类覆盖的虚方法中,可以用inherited调用父类的实现,但有时候我们并不需要父类的实现,而是想跃过父类直接调用祖先类的方法。

举个例子,假设有三个类,实现如下:

type

TClassA = class

procedure Proc; virtual;

end;

TClassB = class(TClassA)

procedure Proc; override;

end;

TClassC = class(TClassB)

procedure Proc; override;

end;

implementation

procedure TClassA.Proc;

begin

ShowMessage('Proc of class A');

end;

procedure TClassB.Proc;

begin

ShowMessage('Proc of class B');

end;

procedure TClassC.Proc;

begin

ShowMessage('Proc of class C');

end;

用如下代码调用虚方法Proc:

var

C: TClassA;

begin

C := TClassC.Create;

C.Proc;

C.Free;

end;

我们知道最终调用的是TClassC.Proc;如果在TClassC.Proc中加上Inherited,则TClassB.Proc可以得到调用;但是现在,若想在TClassC.Proc中直接调用TClassA.Proc,该怎么办呢?

解决之道

如果是C++,只需要这样写:TClassC::Proc。

在Delphi却没有办法做到,Delphi不允许我们跃级调用祖先类的方法。尽管如此,还是能从另一个角度来寻求解决的办法。

解决之道就是VMT,每一个类就是一个指向VMT的指针,而VMT的作用其实就是用来保存虚方法的。在VMT的正方向上,列着从祖先类起的所有虚方法,只需要偏移TClassA的VMT到Proc,然后调用之即可。

来看看这个问题是怎么得解决的:

procedure TClassC.Proc;

type

TProc = procedure of object;

var

M: TMethod;

begin

M.Code := PPointer(TClassA)^;

M.Data := Self;

TProc(M)();

ShowMessage('Proc of class C');

end;

执行一次调用,可以看到先弹出:Proc of class A;然后弹出:Proc of class C。这说明TClassA.Proc在TClassC.Proc中被调用到了。

请注意上面的代码,TClassA的VMT上的第0偏移就是Proc的地址,而TClassA继承自TObject,TObject本身也有一些虚方法的,比如AfterConstruction,那么这些是存放在哪里呢?

秘密就在VMT的负偏移上,在System单元中声明了虚表的结构偏移,在负方向上有AfterConstruction的进入点。需要指出的是,System单元中声明了结构偏移正方向的几个已经过时了,第0偏移(vmtQueryInterface)不是存放QueryInterface,而是存放第一个虚方法(除TObject外)。

下面是从帮助上拷下来的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

后记

利用虚表调用虚方法的做法,终究不是安全的,因为Borland(CodeGear)没有向你保证每一个Delphi版本的VMT布局都是一样的。

因此,使用这个方法的时候要慎之又慎。

http://blog.csdn.net/linzhengqun/article/details/1755493

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

我将这个例子改造,变成2个虚函数:

unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, StdCtrls;
type
TForm1 = class(TForm)
Image1: TImage;
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end; type
TClassA = class
procedure Proc; virtual;
procedure second; virtual;
end; TClassB = class(TClassA)
procedure Proc; override;
procedure second; override;
end; TClassC = class(TClassB)
procedure Proc; override;
procedure second; override;
end; var
Form1: TForm1; implementation
{$R *.dfm} procedure TClassA.Proc;
begin
ShowMessage('Proc of class A');
end;
procedure TClassB.Proc;
begin
ShowMessage('Proc of class B');
end; procedure TClassA.second;
begin
ShowMessage('second of class A');
end;
procedure TClassB.second;
begin
ShowMessage('second of class B');
end; procedure TClassC.Proc;
type
TProc = procedure of object;
var
M: TMethod;
P: Pointer;
begin
P := PPointer(TClassA)^;
M.Code := p;
M.Data := Self;
TProc(M)();
ShowMessage('Proc of class C');
end; procedure TClassC.second;
type
TProc = procedure of object;
var
M: TMethod;
p: Pointer;
begin
// P := PPointer(TClassA)^;
// P := Pointer(Integer(p)+4); // 错误:这里试图取得VMT的第二个函数
p:= PPointer(integer(TClassA) + )^;
M.Code := P;
M.Data := Self;
TProc(M)();
ShowMessage('second of class C');
end; procedure TForm1.Button1Click(Sender: TObject);
var
C: TClassA;
begin
C := TClassC.Create;
C.Proc;
C.Free;
end; procedure TForm1.Button2Click(Sender: TObject);
var
C: TClassA;
begin
C := TClassC.Create;
C.Second;
C.Free;
end; end.

// 另外改成P := PPointer(TClassC)^; 也不行,这是为什么?

[石家庄]王烨 2016/3/21 14:36:41
第二个声明一个 procedure of object,然后又去用一个method强制转换
第一个方法的地址
不是VMT
也就是VMT的第一个元素的值
而且他这种方法
只能取virtual的
不能取dynamic的
而且他这种,如果带参数咋办?
用我那种吧

这样不需要按照Method方式调用

访问祖先类的虚方法(直接访问祖先类的VMT,但是这种方法在新版本中未必可靠)的更多相关文章

  1. 【译文】 C#面向对象的基本概念 (Basic C# OOP Concept) 第一部分(类,对象,变量,方法,访问修饰符)

    译文出处:http://www.codeproject.com/Articles/838365/Basic-Csharp-OOP-Concept 相关文档:http://files.cnblogs.c ...

  2. Java 访问限制符 在同一包中或在不同包中:使用类创建对象的权限 & 对象访问成员变量与方法的权限 & 继承的权限 & 深入理解protected权限

    一.实例成员与类成员 1. 当类的字节码被加载到内存, 类中类变量.类方法即被分配了相应内存空间.入口地址(所有对象共享). 2. 当该类创建对象后,类中实例变量被分配内存(不同对象的实例变量互不相同 ...

  3. java中,方法可以访问他的类对象的任何私有特性

    java中,方法可以访问他的类对象的任何私有特性 读一本书(Core Java for the Impatient)时,发现这个注意,以前的时候没有在意,今天仔细想想发现记忆不深刻.记录一下 下面代码 ...

  4. C#类、方法的访问修饰符

    这篇文章主要介绍了C#类的访问修饰符用法,较为详细的分析了C#类的访问修饰符概念与用法,具有一定的参考借鉴价值,需要的朋友可以参考下 本文详细分析了C#类的访问修饰符用法,分享给大家供大家参考.具体用 ...

  5. accessor mothod mutator mothod 更改器方法 访问器方法 类的方法可以访问类的任何一个对象的私有域!

    LocalDate.plusDate String.toUpperCase GregorianCalendar.add import java.time.*; public class Calenda ...

  6. LindAgile~缓存拦截器支持类的虚方法了

    写它的原因 之前写过一个缓存拦截器,主要在方法上添加CachingAspect特性之后,它的返回值就可以被缓存下来,下次访问时直接从缓存中返回结果,而它有一个前提,就是你的方法需要是一个接口方法,缓存 ...

  7. 《Effective Java》笔记 使类和成员的可访问性最小化

    类和接口 第13条 使类和成员的可访问性最小化 1.设计良好的模块会隐藏所有的实现细节,把它的API与实现清晰的隔离开来,模块之间只通过它们的API进行通信,一个模块不需要知道其他模块的内部工作情况: ...

  8. [C++]虚函数-同名访问

    首先来看一下派生类和基类成员同名事的处理规则: 派生类内定义了一个与基类同名的成员,该现象称为同名覆盖,此时,无论派生类内部成员函数还是派生类的对象访问同名成员,如果未加任何特殊标识,则访问派生类中重 ...

  9. Effective Java:Ch4_Class:Item13_最小化类及其成员的可访问性

    要区别一个模块是否设计良好,最重要的因素是,对于其他模块而言该模块隐藏其内部数据和其他实现细节的程度.设计良好的模块应该隐藏所有实现细节,将API与其实现清晰地隔离开来.这样,模块之间通过他们的API ...

随机推荐

  1. python-Day3-set 集合-counter计数器-默认字典(defaultdict) -可命名元组(namedtuple)-有序字典(orderedDict)-双向队列(deque)--Queue单项队列--深浅拷贝---函数参数

    上节内容回顾:C语言为什么比起他语言块,因为C 会把代码变异成机器码Pyhton 的 .pyc文件是什么python 把.py文件编译成的.pyc文件是Python的字节码, 字符串本质是 字符数组, ...

  2. 我的Python成长之路---第一天---Python基础(5)---2015年12月26日(雾霾)

    六.流程控制 与C语言不通的事Python的流程控制的代码块不是用{}花括号表示的,而是用强制缩进来,而且缩进必须一致,官方推荐是使用4个空格,不建议使用使用tab(制表符)做缩进,一是不同的系统ta ...

  3. 在Android开发中,定时器一般有以下3种实现方法

    在Android开发中,定时器一般有以下3种实现方法: 原文地址http://www.360doc.com/content/12/0619/13/87000_219180978.shtml 一.采用H ...

  4. Python 数据分析(二 本实验将学习利用 Python 数据聚合与分组运算,时间序列,金融与经济数据应用等相关知识

    Python 数据分析(二) 本实验将学习利用 Python 数据聚合与分组运算,时间序列,金融与经济数据应用等相关知识 第1节 groupby 技术 第2节 数据聚合 第3节 分组级运算和转换 第4 ...

  5. Codeforces Round #270 A~D

    Codeforces Round #270 A. Design Tutorial: Learn from Math time limit per test 1 second memory limit ...

  6. ADO.NET 1创建连接、执行命令

    一无参构造函数的形式: 创建连接.创建命令.执行命令: string connstr = @"server=.;database=TestDataBase;uid=sa;pwd=130988 ...

  7. 在Windows上使用CodeLite+MinGW+Clang进行开发

    前几天听说clang 3.4已经release了,然后我又手痒就折腾一下,在这里记录一下折腾的经过. 在以前就试过clang-cl+VC的开发环境,编译代码到是没发现什么大问题,有不少警告而已,不过c ...

  8. QString与中文,QString与std::wstring的相互转换(使用fromStdWString和u8关键字)

    Qt版本:5.5.1 Qt的QString功能丰富,对非英语语言的支持也不是问题,但支持得不够直接.例如,像 ? 1 QString str("死亡使者赛维"); 这样直接用带中文 ...

  9. 关于 iOS 基础动画

    1,首先,在iOS中,动画有2种,一种是对 UIView 做动画处理,另一种是对 CALayer做动画. 这里我们先要搞清楚 UIView 与 CALayer 之间的关系,UIView 是界面的基础元 ...

  10. Swift - AppDelegate.swift类中默认方法的介绍

    项目创建后,AppDelegate类中默认带有如下几个方法,具体功能如下: 1,应用程序第一次运行时执行 这个方法只有在App第一次运行的时候被执行过一次,每次App从后台激活时都不会再执行该方法. ...