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 hooked virtual method to execute itself in a thread i.e. delay/defer its execution.
For this purpose I use TVirtualMethodInterceptor to intercept virtual methods of a given class. When a virtual method is invoked vmi.OnBefore is fired. This is 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 has a value of PostponeExecution.
The first call to ttc.method1 will trigger OnBefore event (OnBefore_fires_1). The method will not execute, because SomeFlag is PostponeExecution. Therefore a thread will be created which will set SomeFlag to DirectExecute and will invoke the same method again, but within the thread's context.
Then OnBefore fires again (because Instance is the proxified object i.e. the method is the hooked method). This time SomeFlag is DirectExecute and the method will be invoked.
I use proxified object (Instance var) when invoking the method, because I want "Self" to point to it. This way if method1 calls other virtual method of the same class the later will also be automatically executed in a thread.
For this to happen I need to store the flag somewhere i.e. indicate OnBefore's second call what to do. My question is how/where to store "SomeFlag" so it's accessible during the two calls of OnBefore? The solution should be cross-platform. Suggestions/other solutions are also welcome.
I imagine it can be done with VMT patching (link1, link2, link3), but VirtualProtect is a Windows only function so cross-platform requirement would be violated.
Any idea is highly appreciated.
What's this all about:
Imagine you can have this kind of 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 threading and synchronization. No more ducktyping TThread.Synchronize(), TThread.Queue() etc. You just focus on the business logic and call appropriate methods - OnBefore event does the "dirty" job for you. Very close to Await methods in C#.
This is the main idea!
UPDATE: I re-edited the entire question to make it more clear.
Your approach is wrong. What you try to do is basically call the virtual method but without going through the interceptor again. Since the interceptor itself has registered stubs inside the VMT calling the method through invoke will hit the interceptor stub again causing a recursion.
I have done this in the past in the Spring4D interception by doing the invoking on a lower level using the
Rtti.Invoke
routine.This is how you do it:
Since you are calling this asynchronously I left handling of functions out - otherwise you have to check the ReturnType of Method to pass the correct handle, here we are just passing nil.
For the PassArg routine look into the System.Rtt.pas.
Then you just call it like this:
Keep in mind that any var or out parameters are a no go for this approach for obvious reasons.