zoukankan      html  css  js  c++  java
  • [翻译] Virtual method interception 虚方法拦截

    原文地址: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.

     
    以下是输出:

    image_thumb

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

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

    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:当类是多层继承时,拦截的是哪个类的虚拟方法?

  • 相关阅读:
    python利用scapy模块写一个TCP路由追踪和扫描存活IP的脚本
    mitm6:通过IPv6攻破IPv4网络
    php写一个判断是否有cookie的脚本
    python写一个DDos脚本(DOS)
    分布式系统设计系列 -- 基本原理及高可用策略(转)
    在Ubuntu 16.04 上编译安装OpenCV3.2.0(Cmake + python3 + OpenCV3)(转)
    好玩的Raft动画演示,原理秒懂
    全球分布式数据库:Google Spanner(论文翻译)
    分布式系统设计系列 -- 基本原理及高可用策略 (转)
    MySQL更新优化(转)
  • 原文地址:https://www.cnblogs.com/moon25/p/5581671.html
Copyright © 2011-2022 走看看