Store arbitrary data in an object instance

Consider the following example:

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;

      

I want the associated virtual method to execute on a thread, i.e. delay / defer its execution.

For this purpose, I use TVirtualMethodInterceptor to intercept the virtual methods of the given class. When the virtual method is called vmi.OnBefore is fired. This is a simplified representation of my idea:

Call_VirtualMethod (method1) -> OnBefore_fires_1 -> CreateThread_and_InvokeAgain -> OnBefore_fires_2 -> DoInvoke: = true (i.e. directly execute the method)

Explanation:

  • Initially SomeFlag is PostponeExecution.

  • The first call to ttc.method1 will trigger the OnBefore (OnBefore_fires_1) event. The method will fail because SomeFlag PostponeExecution. Therefore, a thread will be created which will set SomeFlag to DirectExecute and call the same method again, but in the context of the thread.

  • Then OnBefore fires again (since the instance is a proxified object i.e. the method is a hook method). This time SomeFlag DirectExecute and method will be called.

I am using a proxified object (var instance) when calling the method because I want "I" to point to it. Thus, if method1 calls another virtual method of the same class, then later it will also be automatically executed in the thread.

To do this, I need to store the flag somewhere, that is, tell OnBefore the second call what to do. My question is how / where to store "SomeFlag" so that it is available during two OnBefore calls? The solution must be cross-platform. Suggestions and other solutions are also welcome.

I guess it can be done using the patch VMT ( link1 , link2 , link3 ), but VirtualProtect - this feature is only for Windows, so cross-platform requirement is violated.

Any idea is much appreciated.

What it is:

Imagine you can have this class in 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;

      

This approach really simplifies threads and synchronization. No more ducktyping TThread.Synchronize (), TThread.Queue (), etc. You just focus on the business logic and call the appropriate methods. The OnBefore event does the dirty work for you. Very close to the Await methods in C #.

This is the main idea!

UPDATE: I've edited the whole question to make it clearer.

+3


source to share


1 answer


Your approach is wrong. What you are trying to do is basically calling the virtual method, but not repeating the interceptor. Since the interceptor itself has registered stubs inside the VMT by calling the method via invoke, it will end up in the interceptor stub again, calling recursion.

I've done this in the past in Spring4D interception by making the call at a lower level using a procedure Rtti.Invoke

.

This is how you do it:

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;

      

Since you are calling this asynchronously, I left the function handling outside - otherwise you need to check the ReturnType of the method to pass in the correct handle, here we are just passing in null.



For the PassArg procedure, check out System.Rtt.pas.

Then you just call it like this:

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;

      

Be aware that any var or out options are not appropriate for this approach for obvious reasons.

+3


source







All Articles