博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[翻译] Virtual method interception 虚方法拦截
阅读量:4920 次
发布时间:2019-06-11

本文共 4807 字,大约阅读时间需要 16 分钟。

原文地址:

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

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. := 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. := 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. := 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: ExceptionAfter 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: ExceptionAfter interception:  before: a = 10  Result = 30  after:  a = 20Inheritance chain while intercepted:  TFoo (01F34DA8)        TFoo (0048BD84)        TObject (004014F0)After unhooking:  before: a = 10  Exception: ExceptionInheritance chain after unhooking:  TFoo (0048BD84)  TObject (004014F0)
 

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

两个问题:

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

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

转载于:https://www.cnblogs.com/moon25/p/5581671.html

你可能感兴趣的文章
[FJOI2016]建筑师(斯特林数)
查看>>
将计算机思维故事化——之操作系统典型调度算法
查看>>
0831 模糊查询,排序查询,聚合函数,时间日期函数,数学函数,字符串函数
查看>>
hive学习3
查看>>
Ubuntu12.10 Server 安装 VirtualBox-4.2.6 (64位) 过程详解
查看>>
11.5 正睿停课训练 Day16
查看>>
随机点名
查看>>
rbac 权限分配, 基于formset实现,批量编辑
查看>>
Asp.Net Web API VS Asp.Net MVC
查看>>
静态邻接表
查看>>
npm安装时提示没读写权限
查看>>
我感觉我要把大佬气死了
查看>>
变量名动态命名和调用
查看>>
看数组中是否存在一个数字,以及输入函数
查看>>
MVC怎么在同一个action返回两个表的数据
查看>>
springcloud14---zuul
查看>>
阻塞队列---ArrayBlockingQueue,LinkedBlockingQueue,DelayQueue源码分析
查看>>
sass05 数据类型,数据运算
查看>>
Git常用命令
查看>>
洛谷 P1644 跳马问题
查看>>