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.
source to share
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.
source to share