考虑以下示例:

type

  TTestClass = class
    public
      procedure method1; virtual;
  end;

  TForm2 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
  public
    vmi: TVirtualMethodInterceptor;
    ttc: TTestClass;
  end;

{ Initially SomeFlag is PostponeExecution }
procedure TForm2.FormCreate(Sender: TObject);
begin

  vmi := TVirtualMethodInterceptor.Create(TTestClass);
  ttc := TTestClass.Create;

  vmi.OnBefore :=
    procedure(Instance: TObject; Method: TRttiMethod;
      const Args: TArray<TValue>; out DoInvoke: Boolean;
        out Result: TValue)
    begin
      if { SomeFlag = DirectExecution } then
        DoInvoke := true
      else
      begin
        { SomeFlag := DirectExecution }
        TThread.CreateAnonymousThread(
          procedure
          begin
            // Invoke() will trigger vmi.OnBefore
            // because Instance is the proxified object
            // I want to keep "Self" to be the proxified object
            Method.Invoke(Instance, Args);
          end
        ).Start;
      end
    end;

  vmi.Proxify(ttc);

  ttc.method1;

end;

{ TTestClass }

procedure TTestClass.method1;
begin
  //  Do something async
end;

procedure TForm2.FormDestroy(Sender: TObject);
begin
  vmi.Unproxify(ttc);
  vmi.Free;
  ttc.Free;
end;

我想要钩子(Hook)虚拟方法在线程中执行自身,即延迟/推迟其执行。

为此,我使用TVirtualMethodInterceptor拦截给定类的虚拟方法。调用虚拟方法时,将触发vmi.OnBefore。这是我的想法的简化表示:

Call_VirtualMethod(方法1)-> OnBefore_fires_1-> CreateThread_and_InvokeAgain-> OnBefore_fires_2-> DoInvoke:= true(即直接执行该方法)

解释:
  • 最初,SomeFlag的值为PostponeExecution。
  • 第一次调用ttc.method1会触发OnBefore事件
    (OnBefore_fires_1)。该方法将不会执行,因为SomeFlag是
    推迟执行。因此,将创建一个线程来设置
    SomeFlag到DirectExecute,将再次调用相同的方法,但是
    在线程的上下文中。
  • 然后再次触发OnBefore(因为Instance是代理对象)
    即该方法是 Hook 方法)。这次是SomeFlag
    DirectExecute和方法将被调用。

  • 调用方法时,我使用了代理对象(实例变量),因为我希望“Self”指向它。这样,如果method1调用了同一类的其他虚拟方法,则后者也将在线程中自动执行。

    为此,我需要将标志存储在某个地方,即指示OnBefore的第二次调用该怎么做。
    我的问题是如何/在哪里存储“SomeFlag”,以便在两次OnBefore调用期间都可以访问它?
    解决方案应该是跨平台的。建议/其他解决方案也欢迎。

    我想可以通过VMT修补(link1link2link3)完成此操作,但是VirtualProtect是仅Windows功能,因此会违反跨平台要求。

    任何想法都受到高度赞赏。

    这是什么一回事:

    想象一下,您可以在Delphi中进行此类学习:
    TBusinessLogic = class
      public
        // Invokes asynchronously
        [InvokeType(Async)]
        procedure QueryDataBase;
    
        // Invokes asynchronously and automatically return asocciated ITask (via OnBefore event)
        [InvokeType(Await)]
        function DownloadFile(AUrl: string): ITask;
    
        // This method touches GUI i.e. synchonized
        [InvokeType(VclSend)]
        procedure UpdateProgressBar(AValue: integer);
    
        // Update GUI via TThread.Queue
        [InvokeType(VclPost)]
        procedure AddTreeviewItem(AText: string);
    
    end;
    
    ...
    
    procedure TBusinessLogic.QueryDataBase;
    begin
      // QueryDataBase is executed ASYNC (QueryDataBase is tagged as Async)
      // Do heavy DB Query here
    
      // Updating GUI is easy, because AddTreeviewItem is tagged as VclPost
      for SQLRec in SQLRecords do
        AddTreeviewItem(SQLRec.FieldByName["CustomerName"].asString);
    end;
    

    这种方法确实简化了线程和同步。不再使用Dtytyping TThread.Synchronize(),TThread.Queue()等。
    您只需关注业务逻辑并调用适当的方法-OnBefore事件为您完成了“肮脏”的工作。与C#中的Await方法非常接近。

    这是主要思想!

    更新:
    我重新编辑了整个问题,以使其更加清晰。

    最佳答案

    您的方法是错误的。您尝试执行的操作基本上是调用虚方法,而无需再次通过拦截器。由于拦截器本身已在VMT内注册了 stub ,因此通过invoke调用该方法将再次命中该拦截器 stub ,从而导致递归。

    我过去在Spring4D拦截中通过使用Rtti.Invoke例程在较低级别上进行调用来做到这一点。

    这是您的操作方式:

    procedure DirectlyInvokeMethod(Instance: TObject; Method: TRttiMethod;
      const Args: TArray<TValue>);
    var
      params: TArray<TRttiParameter>;
      values: TArray<TValue>;
      i: Integer;
    begin
      params := Method.GetParameters;
      SetLength(values, Length(Args) + 1);
      values[0] := Instance;
    
      // convert arguments for Invoke call (like done in the DispatchInvoke methods
      for i := Low(Args) to High(Args) do
        PassArg(params[i], args[i], values[i + 1], Method.CallingConvention); // look at Rtti.pas for PassArg
    
      Rtti.Invoke(Method.CodeAddress, values, Method.CallingConvention, nil);
    end;
    

    由于您是异步调用此函数,因此我省略了对函数的处理-否则,您必须检查Method的ReturnType才能传递正确的句柄,这里我们只是传递nil。

    对于PassArg例程,请查看System.Rtt.pas。

    然后,您可以这样称呼它:
    vmi.OnBefore :=
      procedure(Instance: TObject; Method: TRttiMethod;
        const Args: TArray<TValue>; out DoInvoke: Boolean;
          out Result: TValue)
      begin
        DoInvoke := Method.Parent.Handle = TObject.ClassInfo; // this makes sure you are not intercepting any TObject virtual methods
        if not DoInvoke then // otherwise call asynchronously
          TThread.CreateAnonymousThread(
            procedure
            begin
              DirectlyInvokeMethod(Instance, Method, Args);
            end).Start;
      end;
    

    请记住,出于明显的原因,任何var或out参数都不适合这种方法。

    关于multithreading - 将任意数据存储到对象实例中,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/27058655/

    10-11 21:07