原文地址:
注:基于本人英文水平,以下翻译只是我自己的理解,如对读者造成未知影响,一切后果自负。
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:当类是多层继承时,拦截的是哪个类的虚拟方法?