Closed sglienke closed 4 years ago
Hi, Stefan!
Events (methods) can be intercepted. On the one hand, this is easier to do than interfaces. On the other hand, it's a little more complicated. The main difficulty in intercepting events is to store the overhead. Below I sketched a simple example of how to make a virtual event inheriting from a virtual interface. I hope this will help you understand the features of the library.
program Project1;
{$I TINY.DEFINES.inc}
{$APPTYPE CONSOLE}
uses
SysUtils,
Tiny.Types,
Tiny.Rtti,
Tiny.Invoke;
type
TVirtualMethod<TMethod> = class(TRttiVirtualInterface)
protected
{$ifdef WEAKINSTREF}[Unsafe]{$endif} FValue: TMethod;
public
constructor Create(const ACallback: TRttiVirtualMethodInvokeEvent);
property Value: TMethod read FValue;
end;
constructor TVirtualMethod<TMethod>.Create(const ACallback: TRttiVirtualMethodInvokeEvent);
var
LTypeData: PTypeData;
LSignature: TRttiSignature;
LMethods: TRttiVirtualMethodDataDynArray;
LSystemMethod: ^System.TMethod;
begin
LTypeData := GetTypeData(TypeInfo(TMethod));
LSignature.Init(LTypeData.MethodSignature, @DefaultContext);
SetLength(LMethods, 1);
LMethods[0].InterceptFunc := LSignature.OptimalInterceptFunc;
LMethods[0].Method.Name := Pointer(@PTypeInfo(TypeInfo(TMethod)).Name);
LMethods[0].Method.Index := 0;
LMethods[0].Method.Signature := InternalCopySignature(LSignature);
LMethods[0].Method.Context := nil;
FDefaultInvokeEvent := ACallback;
LMethods[0].Callback := GetCallback(FDefaultInvokeEvent);
CreateDirect(nil, LMethods);
LSystemMethod := Pointer(@FValue);
LSystemMethod.Data := @FTable.Vmt;
LSystemMethod.Code := FTable.Vmt[3];
end;
type
TMyMethod = function(const X, Y, Z: Integer): Integer of object;
var
MyMethod: TMyMethod;
MyMethodStorage: IInterface;
R, X, Y, Z: Integer;
begin
MyMethodStorage := TVirtualMethod<TMyMethod>.Create(
function(const AMethod: Tiny.Invoke.TRttiVirtualMethod;
const AArgs: TArray<Tiny.Rtti.TValue>; const AReturnAddress: Pointer): TValue
begin
Result := AArgs[1].AsInteger + AArgs[2].AsInteger + AArgs[3].AsInteger;
end) as IInterface;
MyMethod := (MyMethodStorage as TVirtualMethod<TMyMethod>).Value;
X := 1;
Y := 2;
Z := 3;
Write('Summ(', X, ', ', Y, ', ', Z, ') = ');
R := MyMethod(X, Y, Z);
Writeln(R);
Write('Press Enter to quit');
Readln;
end.
Thank you - I tried without putting it into the TRttiVirtualInterface instance because that seemed like unnecessary overhead - can I assume that the intercept stubs that you generate are generated specifically for the layout of that class so that is why I ended up getting AVs when trying this?
Edit: Ah, got it - they are for the layout of TRttiVirtualInterfaceTable - when I use that it works.
Quite right. The interception was done specifically for virtual interfaces. Because virtual interfaces are more complex and more commonly used.
There are 3 stages of interception:
TInterception = packed record
MethodData: PRttiVirtualMethodData;
Data: packed record end;
// System.TMethod(MyMethod).Data := @Interception.Data;
// System.TMethod(MyMethod).Code := Signature.InterceptJumps[0];
end;
asm
...
mov edx, [eax - 4]
add edx, ITEM_OFFSET
jmp [edx] // jmp MethodData.InterceptFunc
end
TRttiVirtualMethodData
contains all necessary information about the method. The InterceptFunc
function is important, it reads and writes registers relative to the dump. It can be determined either through Signature.OptimalInterceptFunc
or Signature.UniversalInterceptFunc
. After the used registers are written, the TRttiVirtualMethodData.Callback
is called, which is user-defined.TValue
). Conversion of dump values to TValue
array occurs in the TRttiVirtualInterface.InternalEventCallback
procedure.Thanks again
I was able to make it work to power the multicast events - performance is nice on Windows (still a little slower than the handwritten asm implementation though) and while a couple times slower on Linux it runs circles around what I had using System.Rtti.
Tell me what multicast events are Maybe I will be able to offer an effective solution
A big request, if there is any code that runs slower in Linux than the standard solution - please open a corresponding ticket. Thank.
Internal implementation is in https://bitbucket.org/sglienke/spring4d/src/develop/Source/Base/Spring.Events.pas and this is where I added the support for Tiny.Invoke (still in a private branch as there is more going on unrelated to this)
Basically the observer pattern as a ready to use data type and fully compatible with any Delphi event or method reference type.
And no I am just saying that the same code runs a couple times slower on Linux than it does on Windows - but I blame the poor optimization of the Delphi Linux compiler for that. Using Tiny.Invoke is several magnitudes faster than using System.Rtti which was why I tried it in the first place.
Anonymous method and event with the same signature practically do not require intermediate interception and invocation.
It is very easy to create an event, that invoke an anonymous method:
program Project1;
{$APPTYPE CONSOLE}
type
TMyMethod = function(const X, Y, Z: Integer): Integer of object;
TMyAnonimous = reference to function(const X, Y, Z: Integer): Integer;
var
MyMethod: TMyMethod;
MyAnonimous: TMyAnonimous;
R, X, Y, Z: Integer;
begin
MyAnonimous := function(const X, Y, Z: Integer): Integer
begin
Result := X + Y + Z;
end;
// MyMethod := MyAnonimous;
TMethod(MyMethod).Data := PPointer(@MyAnonimous)^;
TMethod(MyMethod).Code := PPointer(PNativeUInt(TMethod(MyMethod).Data)^ + 3 * SizeOf(Pointer))^;
X := 1;
Y := 2;
Z := 3;
Write('Summ(', X, ', ', Y, ', ', Z, ') = ');
R := MyMethod(X, Y, Z);
Writeln(R);
Write('Press Enter to quit');
Readln;
end.
In newer versions of Delphi, the inverse transformation is even easier:
MyAnonimous := MyMethod;
But since what version of Delphi this construction works - I don't know. You may have to do the transformation magic manually.
You are missing the point but that's ok.
Hi,
I was looking into using Tiny.Invoke for creating the necessary code for my multicast events in Spring4D for non windows platforms which currently use the dead slow System.Rtti.
However I cannot find a way how to intercept a tkMethod type. I need an intercept callback for a TMethodSignature similar to how I can see in the VirtualInterface Demo.
Regards