Jump to content
pyscripter

TMethodImplementation and its uses

Recommended Posts

Posted (edited)

System.Rtti contains a lesser known, but powerful class TMethodImplementation, which is basically hidden and used in TVirtualMethodInterceptor.  Outside this use, it cannot be created directly and it can only be accessed by TRttiMethod.CreateImplementation.   

 

I recently discovered that Spring4d extends its use through a helper for TRttiInvokableType, so it can be used with standalone procedures and functions as well as with events.  Here is a small console app showing how it can be used:

 

program MethodImplementationTest;
{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils,
  System.TypInfo,
  System.Rtti,
  Spring;

type
  TSimpleProc = function(I: Integer): Integer;

procedure ImplCallback(UserData: Pointer; const Args: TArray<TValue>;
    out Result: TValue);
begin
  WriteLn('From Implementation');
  Result := Args[0].AsInteger * 2;
end;

var
  Proc: TSimpleProc;
begin
  ReportMemoryLeaksOnShutdown := True;
  try
    var RTTIContext := TRttiContext.Create;
    var RTTIType := RTTIContext.GetType(TypeInfo(TSimpleProc));
    var ProcImplementation := (RTTIType as TRttiInvokableType).CreateImplementation(nil, ImplCallback);

    Proc := TSimpleProc(ProcImplementation.CodeAddress);

    WriteLn(Proc(2));
    ProcImplementation.Free;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  ReadLn;
end.

Output:

From Implementation
4

Simple and easy.  Spring4D is full of gems.  I wish the above functionality was part of the standard library.

 

I can think of many uses:

  • Python4Delphi contains a unit MethodCallback which uses complex assembly to convert methods into stubs that can be used with external C libraries.  It could be rewritten using TMethodImplementation, without assembly code.
  •  Also in Python4Delphi, the implementation of Delphi events using python code could be very much simplified and generalized using TMethodImplementation.  You can create Delphi functions, procedures and methods, event handlers on the fly that are implemented by python code.
  • Similarly functionality can be provided with other scripting languages.

 

Edited by pyscripter
  • Like 2

Share this post


Link to post

FWIW I only use this to make my multicast events work on platforms where I don't have exactly that: handcrafted assembly code to do the parameter passing. And these run hundreds of times faster than using RTTI.

You can turn on using RTTI instead of the asm code in Spring.Events 

 

Here is a simple benchmark result:

asm (win32)
Event invokes/ms: 14925

rtti
Event invokes/ms: 205

I have already started digging more into the TMethodImplementation class to optimize its use for those platforms where I don't have the handcrafted asm.

There are also libraries that avoid the overhead of the Delphi RTTI to do dynamic invocation by handling all the low level details: https://github.com/d-mozulyov/Tiny.Library#rtti but that requires doing the parameter passing via some stubs written in c asm

  • Like 1
  • Thanks 1

Share this post


Link to post
Posted (edited)

Here is an example of making an event handler on the fly using spring4d, TMethodImplementation and a callback routine, in case anyone has a use for it.

 

program MethodImplementationTest2;
{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils,
  System.TypInfo,
  System.Rtti,
  Spring;

type
  TSimpleEvent = function(I: Integer): Integer of object;

  TTestClass = class
  private
    fEvent: TSimpleEvent;
  published
    property Event: TSimpleEvent read fEvent write fEvent;
  end;

procedure ImplCallback(UserData: Pointer; const Args: TArray<TValue>;
    out Result: TValue);
begin
  WriteLn('From Implementation');
  WriteLn(Args[0].AsObject.ClassName);
  Result := Args[1].AsInteger * 2;
end;

begin
  ReportMemoryLeaksOnShutdown := True;
  var TestObj := Shared.Make(TTestClass.Create)();
  try
    var RTTIContext := TRttiContext.Create;
    var RTTIType := RTTIContext.GetType(TypeInfo(TTestClass));
    var RTTIMethodType := RTTIType.GetProperty('Event').PropertyType;

    var MethodImplementation := (RTTIMethodType as TRttiInvokableType).CreateImplementation(nil, ImplCallback);
    var Method: TMethod;
    Method.Code := MethodImplementation.CodeAddress;
    Method.Data := TestObj;
    SetMethodProp(TestObj, 'Event', Method);

    WriteLn(TestObj.Event(2));
    MethodImplementation.Free;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  ReadLn;
end.

Output:

 

From Implementation
TTestClass
4

 

Edited by pyscripter

Share this post


Link to post
Posted (edited)

Hello,

 

While technically this seems cool, why is this particularly useful? I see that I can create an event handler without an explicit object that will implement the event handler method. Why would I need to do this?

 

If I needed this, would I not simply design my class to accept anonymous methods? Or is this for a trick to allow classes not designed to handle anonymous methods to actually handle them?

 

Edited by Dave Novo

Share this post


Link to post
Posted (edited)
1 hour ago, Dave Novo said:

While technically this seems cool, why is this particularly useful? I see that I can create an event handler without an explicit object that will implement the event handler method. Why would I need to do this?

 

Did you read my first post?  It answers your question providing some use cases.  Spring4d also uses this for supporting multicast events.

Edited by pyscripter

Share this post


Link to post

Of course I read your post. But I am unclear what could not be handled using anonymous methods?

i.e. while the code in

procedure ImplCallback(UserData: Pointer; const Args: TArray<TValue>;
    out Result: TValue);

 

has a very general method signature, in actuality every implementation has to know exactly how many args are going to come in the args array, and exactly what they are going to mean. So why can you not wrap the python code in an anonymous method and then pass the anon method to delphi.

 

I am unclear of what the example of why you would need a method stub.

 

 

Share this post


Link to post
Posted (edited)

In using delphivcl.pyd  the user can attach an arbitrary python method as an event handler to any vcl component at runtime.   The event handler needs to be created on-the-fly, since you do not know in advance what python method will attach to which events.   TMethodImplementation can do just that.  Create an event handler of the right type when it is need.  In this use case the callback would translate the Args to their python equivalents and call the python code.

 

The callback can be an anonymous method, but this is not important.  The important thing is that the event handler needs to be created at runtime and not at compile time.  Currently  python4delphi needs to provide helper classes for each event type (TNotifyEventHandler for TNotifyEvent etc.).  All this code can go away and have just one generic implementation of event handling that can deal with any kind of event.

 

For example in the code below:

 

class MyForm(Form):
    def __init__(self, owner):
        self.timer = Timer(self)
        self.timer.Interval = 10
        self.timer.OnTimer = self.__on_timer
        self.timer.Enabled = True

    def __on_timer(self, sender):
        self.CheckBox1.Enabled = True

when self.timer.OnTimer = self.__on_timer is executed an event handler needs to be attached to the OnTimer event of a Delphi timer object that will run the python method.

 

Edited by pyscripter

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×