原文地址:http://blog.barrkel.com/2010/09/virtual-method-interception.html

注:基于本人英文水平,以下翻译只是我自己的理解,如对读者造成未知影响,一切后果自负。

Delphi XE在rtti.pas单元有一个新的类型TVirtualMethodInterceptor。它最初是设计用于DataSnap的认证场景(虽然我不认为只能用在这里)。

这个类做了什么?从本质上讲,它在运行时动态地创建了一种衍生metaclass,并重写父类的虚方法,通过创建一个新的虚拟方法表和并填充到父类。用户可以拦截虚拟函数调用,在代码实现中改变参数,改变返回值,拦截和中断异常或抛出新的异常,或完全取代方法。在概念上,它有点类似于.NET和Java中的动态代理。它就像在运行时从一个类中派生出来的新类,重写虚方法(但不添加新的字段属性),然后将一个实例的运行类型更改为这个新的派生类。

为什么要这么做?两个目的:测试和远程处理。(“虚拟方法拦截”最初用在DataSnap认证部分)。

用一个例子开始:

 

uses SysUtils, Rtti;

{$APPTYPE console}

type

TFoo = class

// 修改x 的值

function Frob(var x: Integer): Integer; virtual;

end;

function TFoo.Frob(var x: Integer): Integer;

begin

x := x * 2;

Result := x + 10;

end;

procedure WorkWithFoo(Foo: TFoo);

var

a, b: Integer;

begin

a := 10;

Writeln(' a = ', a);

try

b := Foo.Frob(a);

Writeln(' result = ', b);

Writeln(' a = ', a);

except

on e: Exception do

Writeln(' 异常: ', e.ClassName);

end;

end;

procedure P;

var

Foo: TFoo;

vmi: TVirtualMethodInterceptor;

begin

vmi := nil;

Foo := TFoo.Create;

try

Writeln('拦截以前:');

WorkWithFoo(Foo);

vmi := TVirtualMethodInterceptor.Create(Foo.ClassType);

vmi.OnBefore := procedure(Instance: TObject; Method: TRttiMethod;

const Args: TArray<TValue>; out DoInvoke: Boolean; out Result: TValue)

var

i: Integer;

begin

Write('[之前] 调用方法', Method.Name, ' 参数: ');

for i := 0 to Length(Args) - 1 do

Write(Args[i].ToString, ' ');

Writeln;

end;

// 改变 foo 实例的 metaclass pointer

vmi.Proxify(Foo);

//所有的虚方法调用以前,都调用了OnBefore事件

Writeln('拦截以后:');

WorkWithFoo(Foo);

finally

Foo.Free;

vmi.Free;

end;

end;

begin

P;

Readln;

end.

 
以下是输出:

你会发现它拦截所有的虚拟方法,包括那些所谓的销毁过程中,不只是我们自己声明的。(析构函数本身是不包括在内。)

我可以完全改变方法的实现,并跳过调用方法的主体:

procedure P;

var

Foo: TFoo;

vmi: TVirtualMethodInterceptor;

ctx: TRttiContext;

m: TRttiMethod;

begin

vmi := nil;

Foo := TFoo.Create;

try

Writeln('拦截以前:');

WorkWithFoo(Foo);

vmi := TVirtualMethodInterceptor.Create(Foo.ClassType);

m := ctx.GetType(TFoo).GetMethod('Frob');

vmi.OnBefore := procedure(Instance: TObject; Method: TRttiMethod;

const Args: TArray<TValue>; out DoInvoke: Boolean; out Result: TValue)

begin

if Method = m then

begin

DoInvoke := False; //原方法的逻辑不调用了

Result := 42;

Args[0] := -Args[0].AsInteger;

end;

end;

在这里,中断了方法的调用并把返回结果赋值为42,同时修改第一个参数的值:
拦截前:
before: a = 10
Result = 30
after: a = 20
拦截后:
before: a = 10
Result = 42
after: a = –10
 
可以通过一个异常来中断方法的调用:
vmi.OnBefore := procedure(Instance: TObject; Method: TRttiMethod;
const Args: TArray; out DoInvoke: Boolean; out Result: TValue)
begin
if Method = m then
raise Exception.Create('Aborting');
end;
输出:
拦截前:
before: a = 10
Result = 30
after: a = 20
拦截后:
before: a = 10
Exception: Exception
 
不局限于在方法调用前拦截,同时也可以在方法调用后拦截,并可以修改参数和返回值:
m := ctx.GetType(TFoo).GetMethod('Frob');
vmi.OnAfter := procedure(Instance: TObject; Method: TRttiMethod;
const Args: TArray; var Result: TValue)
begin
if Method = m then
Result := Result.AsInteger + 1000000;
end;
以下是输出:
拦截前:
before: a = 10
Result = 30
after: a = 20
拦截后:
before: a = 10
Result = 1000030
after: a = 20
 
如果虚拟方法中抛出了一个异常,可以屏蔽掉这个异常:
function TFoo.Frob(var x: Integer): Integer;
begin
raise Exception.Create('Abort');
end; // ...
m := ctx.GetType(TFoo).GetMethod('Frob');
vmi.OnException := procedure(Instance: TObject; Method: TRttiMethod;
const Args: TArray; out RaiseException: Boolean;
TheException: Exception; out Result: TValue)
begin
if Method = m then
begin
RaiseException := False; //屏蔽掉原方法中的异常
Args[0] := Args[0].AsInteger * 2;
Result := Args[0].AsInteger + 10;
end;
end;
输出:
Before hackery:
before: a = 10
Exception: Exception
After interception:
before: a = 10
Result = 30
after: a = 20
 
有件事要知道,类TVirtualMethodInterceptor 是没有的, 它通过拦截对象工作,拦截需要一些内存开销,但这是很少的:
PPointer(foo)^ := vmi.OriginalClass;
 
另一个指针: 类的继承链是被挂钩过程改变了。这可以很容易地显示:
//...
Writeln('After interception:');
WorkWithFoo(foo); Writeln('Inheritance chain while intercepted:');
cls := foo.ClassType;
while cls <> nil do
begin
Writeln(Format(' %s (%p)', [cls.ClassName, Pointer(cls)]));
cls := cls.ClassParent;
end; PPointer(foo)^ := vmi.OriginalClass; Writeln('After unhooking:');
WorkWithFoo(foo); Writeln('Inheritance chain after unhooking:');
cls := foo.ClassType;
while cls <> nil do
begin
Writeln(Format(' %s (%p)', [cls.ClassName, Pointer(cls)]));
cls := cls.ClassParent;
end;
// ...
 
输出:
Before hackery:
before: a = 10
Exception: Exception
After interception:
before: a = 10
Result = 30
after: a = 20
Inheritance chain while intercepted:
TFoo (01F34DA8) //运行时增加的类
TFoo (0048BD84) //vmi.OriginalClass
TObject (004014F0)
After unhooking:
before: a = 10
Exception: Exception
Inheritance chain after unhooking:
TFoo (0048BD84)
TObject (004014F0)
 

该功能主要是前期库的基础修补,但希望你可以看到,这不是太难。(提供一种打补丁的方式)

两个问题:

1:这种方法跟写Helper的区别?

2:当类是多层继承时,拦截的是哪个类的虚拟方法?

[翻译] Virtual method interception 虚方法拦截的更多相关文章

  1. 抽象方法(abstract method) 和 虚方法 (virtual method), 重载(overload) 和 重写(override)的区别于联系

    1. 抽象方法 (abstract method) 在抽象类中,可以存在没有实现的方法,只是该方法必须声明为abstract抽象方法. 在继承此抽象类的类中,通过给方法加上override关键字来实现 ...

  2. c# 虚方法(virtual)与 多态(Polymorphism)

    using System; using System.Collections.Generic; using System.Linq; using System.Text; //虚方法(virtual) ...

  3. (翻译) 使用Unity进行AOP对象拦截

    Unity 是一款知名的依赖注入容器( dependency injection container) ,其支持通过自定义扩展来扩充功能. 在Unity软件包内 默认包含了一个对象拦截(Interce ...

  4. C#抽象类、抽象方法、虚方法

    定义抽象类和抽象方法: abstract 抽象类特点: 1.不能初始化的类被叫做抽象类,它们只提供部分实现,但是另一个类可以继承它并且能创建它们的实例 2.一个抽象类可以包含抽象和非抽象方法,当一个类 ...

  5. C#多态--虚方法实现多态

    1.虚方法提供一种默认实现,子类可以选择是否重写,如果不重写,那么就使用父类已经实现的方法.(重写可以改变方法的指针) 如果需要改变类型指针,那么需要做方法的重写: 1.如果子类方法是重写方法,那么系 ...

  6. Csharp多态的实现(虚方法)

    1.什么是抽象类 1.1虚方法是用virtual修饰,在子类中用override进行重写 1.2虚方法是一个方法,放在类里面(可以再下面的代码中看到) 1.3虚方法可以 重写,也可以不重写(这个可以再 ...

  7. C#通过虚方法实现方法重写—多态。

    class Program { //希望person存的是哪个类的对象就调用哪个类的方法 //第一步 将父类中对应方法家virtual关键字 变为虚方法(子类可重写) //子类中方法用override ...

  8. C#之抽象类、虚方法、重写、接口、密封类

    前言    学了这么长时间的C#,我想说对于这个东东还是不是特别了解它,以至于让我频频郁闷.每次敲代码的时候都没有一种随心所欲的感觉.所以不得不在网上搜集一些资料,look 了 look~ 内容   ...

  9. C#Protected和多态(虚方法)

    Protected 在基类中定义后,能被派生类调用,但是不能被其他类调用. virtual 在基类中定义后,在派生类中能被重写. using System; using System.Collecti ...

随机推荐

  1. 201671010140. 2016-2017-2 《Java程序设计》java学习第四周

    java学习第四周体会         本周,与前几周不同的是,老师没有进行课堂测试,而是上了一节课,回顾与总结了之前三周所学的知识,也是因为这节课,我注意到了之前学习中忽略的一些细节,和之前学习方法 ...

  2. tensorflow生成随机数的操作 tf.random_normal & tf.random_uniform & tf.truncated_normal & tf.random_shuffle

    tf.random_normal 从正态分布输出随机值. random_normal(shape,mean=0.0,stddev=1.0,dtype=tf.float32,seed=None,name ...

  3. iperf——网络性能测试工具

    一.前言 工作中遇到需要测试Linux服务器网卡占用率的场景,查阅资料后,发现iperf是一款合适的网络测速工具. 下面讲解一下如何使用iperf做网络性能测试. 二.基础知识 先补充一些基础知识: ...

  4. HAproxy-1.6.X 安装部署

    1. 源码包下载及安装 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 root@iZ23tsilmb7Z:/usr/local ...

  5. phpStudy5——php导入其他php文件(php文件的引入)

    前言: 通过前边几个例子,相信大家都会有一个疑惑了,就是每个请求数据库的php页面,都要写一次连接数据库的代码,这个肯定是有违代码复用原则的.那么怎么解决这个问题呢? 在php中可以通过include ...

  6. VMware安装Windows注意

    安装Windows通用步骤,分区,重建分区表,重写MBR引导.安装即可. VMware安装Windows 如果进不了CM/ROM,在.vmx文件里: 加入一行:bios.forceSetupOnce ...

  7. ajax原理以及优缺点(转)

    1.ajax技术的背景不可否认,ajax技术的流行得益于google的大力推广,正是由于google earth.google suggest以及gmail等对ajax技术的广泛应用,催生了ajax的 ...

  8. 给Array添加去重原型方法

    Array.prototype.unique = function(){ var newArray = []; var oldArray = this; if(oldArray.length<= ...

  9. 原生 JS 实现移动端 Touch 滑动反弹

    什么是 Touch滑动?就是类似于 PC端的滚动事件,但是在移动端是没有滚动事件的,所以就要用到 Touch事件结合 js去实现,效果如下: 1. 准备工作 什么是移动端的 Touch事件?在移动端 ...

  10. Nginx的使用(反向代理,负载均衡)

    在我目前的工作内容中,接触到Nginx的用处无外乎两点: 1. 反向代理,解决前端跨域的问题 工作内容有门户的概念,就是将各个子系统集成到门户里,在门户里面访问,这样就很容易造成跨域的问题 那么解决的 ...