Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2022-09

Get TMethod from function reference

14 messages · 4 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Hairy Pixels

A function reference can call a class method but can you get the TMethod data from function references? It was possible with “is object” to cast to TMethod but that doesn’t seem to be possible with references.

=========

type
  TMyClass = class
    constructor Create;
    procedure DoThis;
  end;
  constructor TMyClass.Create;
  var
    proc: reference to procedure;
  begin
    proc := @DoThis;
    writeln(Assigned(PMethod(proc)^.data)); // FALSE
  end;

Regards,
Ryan Joseph

Michael Van Canneyt
to Hairy Pixels
Hairy Pixels wrote 17 lines
A function reference can call a class method but can you get the TMethod data from function references? It was possible with “is object” to cast to TMethod but that doesn’t seem to be possible with references. ========= type TMyClass = class constructor Create; procedure DoThis; end; constructor TMyClass.Create; var proc: reference to procedure; begin proc := @DoThis; writeln(Assigned(PMethod(proc)^.data)); // FALSE end;

To me it seems logical that you cannot do this, since a function reference is actually an interface, and there is no actual object to back it up.

Your function reference could also be a regular procedure (no data at all) or even a local procedure.

Michael.

Hairy Pixels
to Michael Van Canneyt
Show quoted text 7 lines
On Sep 8, 2022, at 9:09 AM, Michael Van Canneyt wrote: To me it seems logical that you cannot do this, since a function reference is actually an interface, and there is no actual object to back it up. Your function reference could also be a regular procedure (no data at all) or even a local procedure.

I thought it keeps the method data in the interface so it could dispatch it later. How would it work otherwise?

Regards,
Ryan Joseph

Michael Van Canneyt
to Hairy Pixels
Hairy Pixels wrote 11 lines
> On Sep 8, 2022, at 9:09 AM, Michael Van Canneyt wrote: > > To me it seems logical that you cannot do this, since a function reference is actually an > interface, and there is no actual object to back it up. > > Your function reference could also be a regular procedure (no data at all) or even a local > procedure. I thought it keeps the method data in the interface so it could dispatch it later. How would it work otherwise?

It must keep this information somewhere, but this does not mean you can typecast it to a TMethod.

Michael.

Hairy Pixels
to Michael Van Canneyt
On Sep 8, 2022, at 11:14 AM, Michael Van Canneyt via fpc-pascal wrote: It must keep this information somewhere, but this does not mean you can typecast it to a TMethod.

Indeed. I wonder if this information could be exposed by some method in the interface? It would be useful to introspect the target class like you can do with “of object”.

Regards,
Ryan Joseph

Ondrej Pokorny
to Hairy Pixels
Hairy Pixels wrote
> On Sep 8, 2022, at 11:14 AM, Michael Van Canneyt via fpc-pascal wrote: > > It must keep this information somewhere, but this does not mean you can typecast it to a TMethod. Indeed. I wonder if this information could be exposed by some method in the interface? It would be useful to introspect the target class like you can do with “of object”.

See the announcement email from Sven to the fpc-pascal mailing list: https://lists.freepascal.org/pipermail/fpc-pascal/2022-May/060507.html

They [function references] get their great power from a point that is for once *not*
considered an implementation detail: function references are in fact internally declared as reference counted interfaces with a single Invoke() method of the provided signature. So the above examples are in fact declared like this:

=== code begin ===

type
    TProcLongInt = interface(IInterface)
      procedure Invoke(aArg: LongInt); stdcall; overload;
    end;

You should be able to get the Invoke procedure pointer from the RTTI.

Ondrej

Sven Barth
to Hairy Pixels: A function reference can call a class method but can you get the TMethod data from…
Hairy Pixels wrote 17 lines
A function reference can call a class method but can you get the TMethod data from function references? It was possible with “is object” to cast to TMethod but that doesn’t seem to be possible with references. ========= type TMyClass = class constructor Create; procedure DoThis; end; constructor TMyClass.Create; var proc: reference to procedure; begin proc := @DoThis; writeln(Assigned(PMethod(proc)^.data)); // FALSE end;

As mentioned elsewhere function references are in fact interfaces. And you can't retrieve a method pointer to a interface function, cause relying on that would result in either memory leaks or premature release of the interface, because TMethod only contains a Pointer for the Data field that does not support managed types.

You can get a reference to the raw interface by doing this however:

=== code begin ===

var
   proc: reference to procedure;
   i: IUnknown;
begin
    i := IUnknown(PPointer(@proc)^);
end.

=== code end ===

Though I don't know what you hope to achieve with this...

Regards,
Sven

Hairy Pixels
to Sven Barth
Show quoted text 18 lines
On Sep 15, 2022, at 12:28 PM, Sven Barth wrote: As mentioned elsewhere function references are in fact interfaces. And you can't retrieve a method pointer to a interface function, cause relying on that would result in either memory leaks or premature release of the interface, because TMethod only contains a Pointer for the Data field that does not support managed types. You can get a reference to the raw interface by doing this however: === code begin === var proc: reference to procedure; i: IUnknown; begin i := IUnknown(PPointer(@proc)^); end. === code end === Though I don't know what you hope to achieve with this...

Ok so what’s happening is I had a deferred dispatch library that used many different callback types and now I want to unify it using references. Problem is, with the old design I used “of object” types so I was able to get the target class and use it do to things like cancel all actions that were associated with a particular class.

I don’t understand how memory loss could be in play. If the function reference is stored and not freed it should have the information needed to invoke a method and in theory you should be able to retrieve it anytime, providing the interface had a public method for this (which would be a nice extension to add). How is that not correct?

Regards,
Ryan Joseph

Sven Barth
to Hairy Pixels
Hairy Pixels wrote 20 lines
> On Sep 15, 2022, at 12:28 PM, Sven Barth wrote: > > As mentioned elsewhere function references are in fact interfaces. And you can't retrieve a method pointer to a interface function, cause relying on that would result in either memory leaks or premature release of the interface, because TMethod only contains a Pointer for the Data field that does not support managed types. > > You can get a reference to the raw interface by doing this however: > > === code begin === > > var > proc: reference to procedure; > i: IUnknown; > begin > i := IUnknown(PPointer(@proc)^); > end. > > === code end === > > Though I don't know what you hope to achieve with this... Ok so what’s happening is I had a deferred dispatch library that used many different callback types and now I want to unify it using references. Problem is, with the old design I used “of object” types so I was able to get the target class and use it do to things like cancel all actions that were associated with a particular class.

Unless you inherit from a function reference as mentioned by Ondrej then all you would get is object instance the compiler constructs implicitely so it wouldn't be much use to you anyway.

I don’t understand how memory loss could be in play. If the function reference is stored and not freed it should have the information needed to invoke a method and in theory you should be able to retrieve it anytime, providing the interface had a public method for this (which would be a nice extension to add). How is that not correct?

And that's the thing: you need to keep the function reference itself around. Or increase the reference count of the interface. In both cases you need to make sure that you clear it up correctly. Otherwise you'll have a memory leak. And if you don't do all that then the function reference will go out of scope and be released.

Regards,
Sven

Hairy Pixels
to Ondrej Pokorny: See the announcement email from Sven to the fpc-pascal mailing list:…
On Sep 11, 2022, at 3:28 PM, Ondrej Pokorny via fpc-pascal wrote: You should be able to get the Invoke procedure pointer from the RTTI.

Sorry Sven mentioned this I must have glossed over it. So yes I am storing the function reference in a class so it’s not going to get freed.

Given this example here can you explain how to get the procedure pointer from the RTTI? Even so if you get the procedure pointer you need the class instance also, which reference must be keeping somewhere right?

type
  TProc = reference to procedure;
  TMyClass = class
    procedure DoThis;
  end;
var
  p: TProc;
  c: TMyClass;
begin
  c := TMyClass.Create;
  p := c.DoThis;
  // Can I get the refefence to "c" back using the RTTI?
end.

Regards,
Ryan Joseph

Sven Barth
to Hairy Pixels
Hairy Pixels wrote 23 lines
> On Sep 11, 2022, at 3:28 PM, Ondrej Pokorny via fpc-pascal wrote: > > You should be able to get the Invoke procedure pointer from the RTTI. Sorry Sven mentioned this I must have glossed over it. So yes I am storing the function reference in a class so it’s not going to get freed. Given this example here can you explain how to get the procedure pointer from the RTTI? Even so if you get the procedure pointer you need the class instance also, which reference must be keeping somewhere right? type TProc = reference to procedure; TMyClass = class procedure DoThis; end; var p: TProc; c: TMyClass; begin c := TMyClass.Create; p := c.DoThis; // Can I get the refefence to "c" back using the RTTI? end.

No, you can't get a reference to c, because the compiler will essentially create the following code:

=== code begin ===

type
   TProc = reference to procedure;
   TMyClass = class
     procedure DoThis;
   end;
   TCapturer = class(TInterfacedObject, TProc)
     m: procedure of object;
     procedure Anonymous1;
     procedure TProc.Invoke = Anonymous1;
   end;
var
   p: TProc;
   c: TMyClass;
   capturer: TCapturer;
   capturer_keepalive: IUnknown;
begin
   capturer := TCapturer.Create;
   capturer_keepalive := capturer;
   c := TMyClass.Create;
   capturer.m := @c.DoThis;
   p := capturer as TProc;
end.

=== code end ===

Regards,
Sven

Hairy Pixels
to Sven Barth
Show quoted text 31 lines
On Sep 17, 2022, at 10:57 PM, Sven Barth wrote: === code begin === type TProc = reference to procedure; TMyClass = class procedure DoThis; end; TCapturer = class(TInterfacedObject, TProc) m: procedure of object; procedure Anonymous1; procedure TProc.Invoke = Anonymous1; end; var p: TProc; c: TMyClass; capturer: TCapturer; capturer_keepalive: IUnknown; begin capturer := TCapturer.Create; capturer_keepalive := capturer; c := TMyClass.Create; capturer.m := @c.DoThis; p := capturer as TProc; end. === code end ===

Well if p is effectively TCapturer then you COULD get back TMethod from m (procedure of object) but the compiler doesn’t have a method generated for this.

I read your announcement email again last night and saw that TProc could actually be subclassed which opens up the possibility to read the field (if it’s not intentionally hidden) or use the RTTI perhaps even. Is that possible? If the field “m" is hidden I would say that should be exposed for the purposes of subclassing .

Regards,
Ryan Joseph

Sven Barth
to Hairy Pixels

Hairy Pixels schrieb am So., 18. Sep. 2022, 02:50:

Show quoted text 39 lines
> On Sep 17, 2022, at 10:57 PM, Sven Barth wrote: > > === code begin === > > type > TProc = reference to procedure; > TMyClass = class > procedure DoThis; > end; > > TCapturer = class(TInterfacedObject, TProc) > m: procedure of object; > procedure Anonymous1; > > procedure TProc.Invoke = Anonymous1; > end; > > var > p: TProc; > c: TMyClass; > capturer: TCapturer; > capturer_keepalive: IUnknown; > begin > capturer := TCapturer.Create; > capturer_keepalive := capturer; > c := TMyClass.Create; > capturer.m := @c.DoThis; > p := capturer as TProc; > end. > > === code end === Well if p is effectively TCapturer then you COULD get back TMethod from m (procedure of object) but the compiler doesn’t have a method generated for this.

No, you can't, because if you have multiple such assignments inside one function the capture object is shared and you'd have multiple fields where you wouldn't know to which anonymous function is belongs:

=== code begin ===

Proc1 := @c.OnChange;
Proc2 := @c.OnClick;
Proc3 := @c2.OnChange;

=== code end ===

Will result in the following capture object:

=== code begin ===

type
  TCapturer = class(TInterfacedObject, TProc1, TProc2, TProc3)
    m1: TNotifyEvent;
    m2: TNotifyEvent;
    m3: TNotifyEvent;
    procedure Anonymous1(Sender :TObject); //will call m1
    procedure Anonymous2(Sender: TObject); // will call m2
    procedure Anonymous3(Sender: TObject); // will call m3
  end;

=== code end ===

Please note that you can't rely on the index as other converted anonymous methods (maybe with the same signature) might be intermixed. So you won't know which field is used by what method.

I read your announcement email again last night and saw that TProc could

Show quoted text 5 lines
actually be subclassed which opens up the possibility to read the field (if it’s not intentionally hidden) or use the RTTI perhaps even. Is that possible? If the field “m" is hidden I would say that should be exposed for the purposes of subclassing .

When you subclass a function reference you can not assign any function to it, but you really need to use a self written class type that you use as instance.

Regards,
Sven

Hairy Pixels
to Sven Barth
Show quoted text 27 lines
On Sep 19, 2022, at 4:17 PM, Sven Barth wrote: === code begin === Proc1 := @c.OnChange; Proc2 := @c.OnClick; Proc3 := @c2.OnChange; === code end === Will result in the following capture object: === code begin === type TCapturer = class(TInterfacedObject, TProc1, TProc2, TProc3) m1: TNotifyEvent; m2: TNotifyEvent; m3: TNotifyEvent; procedure Anonymous1(Sender :TObject); //will call m1 procedure Anonymous2(Sender: TObject); // will call m2 procedure Anonymous3(Sender: TObject); // will call m3 end; === code end ===

Interesting, so all reference assignments in a scope generate a single TCapturer? Yeah that makes what I want impossible unless specific support was added to get back one of those fields.

So I guess I need to make a record wrapper for the reference stores the target class. Thankfully you fixed that crash so I can do this now. :)

Regards,
Ryan Joseph