Delphi - Extract setter method's name of a property
Asked Answered
V

2

8

In the following type:

MyClass = class(TInterfacedPersistent)
private
  FMyProperty: Integer;      
published
  procedure setMyProperty(Value: Integer); virtual;
  property MyProperty: Integer read FMyProperty write setMyProperty;

I would like to know the name of the setter method of the "MyProperty" property via RTTI. I've tried the following:

    procedure ShowSetterMethodsNames(pMyObject: TObject);
    var
      vPropList: TPropList;      
      vCount, I: Integer;
    begin
      vCount:= GetPropList(pMyObject.ClassInfo, tkProperties, @vPropList);

      for I:= 0 to vCount -1 do
      begin
          if Assigned(vPropList[I]^.SetProc) then
            ShowMessage(pMyObject.ClassType.MethodName(vPropList[I]^.SetProc));
      end;
    end;

Although the pointer is not nil, all I have is an empty message. Does anybody have some tip to me?

P.S.: I'm using Delphi XE4, and I know I should use extended RTTI instead of classic, but anyway, I can't do what I want in both features... So, any help will be appreciated. Thanks for the replies.


FINAL EDITION, problem solved:

Here is the code working, based in the (help of my friends and...) RTTI unit (DoSetValue method of TRTTIInstanceProperty class):

procedure ShowVirtualSettersNames(pObject: Pointer);
var
  vSetter, vPointer: Pointer;
  vPropList: TArray<TRttiProperty>;
  vProp: TRttiProperty;
begin
  vPropList:= RTTIUtils.ExtractProperties(TObject(pObject).ClassType); // Helper to get properties from a type, based in extended RTTI

  for vProp in vPropList do
  begin
    vPointer:= TRttiInstanceProperty(vProp).PropInfo^.SetProc;
    vPointer:= PPointer(PInteger(pObject)^ + Smallint(vPointer))^;    
    ShowMessage(TObject(pObject).ClassType.MethodName(vPointer));
  end;
end;

This ONLY WORKS FOR VIRTUAL SETTERS, for statics the message is empty. Thanks everyone!

Valer answered 17/7, 2013 at 2:45 Comment(8)
Welcome to Stack Overflow. You should probably include the code you use to assign vPropertyInfo. Otherwise, you can't be sure your result isn't simply due to your having the wrong property information. Also, is the base class of your class relevant, or do you get the same result from TObject or TPersistent? Edit your question to add details.Dillondillow
Edited to show how do I get the PPropInfo. The class is the listed above, it inherits from TInterfacedPersistent, so I can work with RTTI in published properties. Thanks for the help.Valer
You use VIRTUAL method as setter. variables, static methods and virtual ones have different semantics. To check this explanation, remove "virtual" after the setter and check if your code would start to work. Also, i think you put a breakpoint and what the value of IntToHex(vPropList[I]^.SetProc) - it should have shown that it is unbelievably small to be a real pointer.Cecilla
I think your question name should be changed like "how to get effective address of setter" for i believe you do not get it.Cecilla
Maybe... I just asked in a more generic form expecting another solution rather than the one I wrote, if there was one (especially in the new RTTI). I was hoping it was a simple thing to implement (since the RTTI have great resources), but it looks like it isn't. Shouldn't it be? However, I will try to attempt the things you wrote and report it here later. Thank you very much @Arioch 'TheValer
To me it looks you do no need the name. For you the name is just a demonstration that you did acquired the pointer. However the ery ability to make a property with no-procedure setter would make your AOP framework to fail on those propertiesCecilla
Actually the purpose is to intercept only setter methods in a particular scenario of the framework. Advices simply ignores those which aren't setter, and the lightest way to do this without checking the class' metadata all the time is to store these methods' names in a list and check it when intercepted (since TVirtualMethodInterceptor do intercept all virtual methods when a class is proxied, even if they're not property setters). This way I don't need to create a pattern of names for interceptable methods or insert an annotation in each one's declaration.Valer
let us continue this discussion in chatValer
D
0

Read c:\rad studio\9.0\source\rtl\common\System.Rtti.pas

procedure TRttiInstanceProperty.DoSetValue

The setter of the property may be

  • a field (variable)
  • a static procedure
  • a virtual procedure (your case)

And those cases make PropInfo^.SetProc have different semantics of its value. Direct address only applies to static procedures. For virtual methods you add a VMT offset and take the code address from that memory cell, as specified in that code i mentioned (but would not quote for copyright reasons).

Or you just could use TRttiProperty.SetValue and let Delphi do all those little under the hood details. See http://docwiki.embarcadero.com/Libraries/XE2/en/System.Rtti.TRttiProperty.SetValue

EDIT:

  1. the code removed - it did not worked verbatim and the topic starter provided working version.
  2. Regarding and I know I should use Extended RTTI instead of classic one - that is questionable claim. Extended RTTI is known to work noticeably slower than classic one. Dunno if someone did profiled it, but i suspect that is mostly due to the slow code of TValue. You can google and find that lot of people complained of slow TValue implementation and provided alternative ones with fixed efficiency. However since Extended RTTI only uses stock TValue it cannot benefit from those implementations and remains slower than classic one.
Dockage answered 17/7, 2013 at 6:26 Comment(7)
I mistyped it, it's actually in the published section. And though I can capture the properties (names, values, and so...), the "MethodName" function still returbns an empty string :( ...Valer
docwiki.embarcadero.com/Libraries/XE2/en/… bypassing method search ?Cecilla
I tried your last sample, but I have an "Access Violation" at the "P := PPointer(P)^" line.Valer
@DanielChaves 0) FULL text of erros please. don't you conceal critical details! 1) every time before the line with AV you do logging like ShowMessage(IntToHex(Cardinal( P ))) - most probably you'd see nil thereCecilla
@DanielChaves 1) you tired that sample on virtual or static setter? 2) you do demo program with TRttiProperty.SetValue on your property. It should find and call your method. If it does - then you just debug side by side that demo and your program and on each step you notice which values were fetched to which variable. And then you find where they act different and fix your program to work like TRttiProperty.SetValue internals.Cecilla
I did it Arioch, and I edited th original post to add the working code for my problem. Thank you very, very much for the tips, and sorry for the mistakes I did in my posts. :DValer
Glad you solved it. However are you sure you don;t have to make checks (or at least asserts) in ShowVirtualSettersNames that the method is really virtual ? you're kinda doing guts surgery to the compiler and my gut feeling is that there is never too many safety checks in tasks like this.Cecilla
B
2

You can retrieve this method name, if

a) move the method to the published section (classic RTTI works with this section only (more accurately - compiled with {$M+} directive))

b) use right class specifier - MyClass.MethodName, because MethodName is class function

This code works on D7 and XE3:

MyClass = class(TInterfacedPersistent)
private
  FMyProperty: Integer;
published
   procedure setMyProperty(Value: Integer);
   property MyProperty: Integer read FMyProperty write setMyProperty;
end;


procedure TForm1.Button1Click(Sender: TObject);
var
  ppi: PPropInfo;
begin
  ppi := GetPropInfo(MyClass, 'MyProperty');
  ShowMessage(MyClass.MethodName(ppi.SetProc));
end;

P.S. What Delphi version are you using? What about Extended RTTI (since D2010)?

Bacchanal answered 17/7, 2013 at 6:20 Comment(7)
Thank you very much for the reply, I'm going to try this. Can you show an example of how can I do this with the extended RTTI? No need for basics, only the code itself. And thanks in advance for this. :)Valer
Sorry, I don't see a possibility to get setter method in TRttiProperty, it is available only for indexed properties. But.. why do you want to know method name?Bacchanal
I'm implementing a framework applying AOP concepts using the TVirtualMethodInterceptor, to do things such notifying observers when a poperty has changed. So that would be useful for me to implemente a more generic tier and decouple things. However, I'm going to try the first tip. Thank you very much.Valer
I've edited the post, bus unfortunately it still doesn't work :( ... MyClass.MethodName() is returning an empty string.Valer
@DanielChaves that would byte you bad when .SetProc would actually show to the value VARIABLECecilla
Removing the "virtual" keyword did the trick! However doing this I no longer can intercept the method... Why does Delphi insists making our lives so hard?... :( Thank you for the help, MBo.Valer
I've experimented with your first code before all editions - without 'virtual'Bacchanal
D
0

Read c:\rad studio\9.0\source\rtl\common\System.Rtti.pas

procedure TRttiInstanceProperty.DoSetValue

The setter of the property may be

  • a field (variable)
  • a static procedure
  • a virtual procedure (your case)

And those cases make PropInfo^.SetProc have different semantics of its value. Direct address only applies to static procedures. For virtual methods you add a VMT offset and take the code address from that memory cell, as specified in that code i mentioned (but would not quote for copyright reasons).

Or you just could use TRttiProperty.SetValue and let Delphi do all those little under the hood details. See http://docwiki.embarcadero.com/Libraries/XE2/en/System.Rtti.TRttiProperty.SetValue

EDIT:

  1. the code removed - it did not worked verbatim and the topic starter provided working version.
  2. Regarding and I know I should use Extended RTTI instead of classic one - that is questionable claim. Extended RTTI is known to work noticeably slower than classic one. Dunno if someone did profiled it, but i suspect that is mostly due to the slow code of TValue. You can google and find that lot of people complained of slow TValue implementation and provided alternative ones with fixed efficiency. However since Extended RTTI only uses stock TValue it cannot benefit from those implementations and remains slower than classic one.
Dockage answered 17/7, 2013 at 6:26 Comment(7)
I mistyped it, it's actually in the published section. And though I can capture the properties (names, values, and so...), the "MethodName" function still returbns an empty string :( ...Valer
docwiki.embarcadero.com/Libraries/XE2/en/… bypassing method search ?Cecilla
I tried your last sample, but I have an "Access Violation" at the "P := PPointer(P)^" line.Valer
@DanielChaves 0) FULL text of erros please. don't you conceal critical details! 1) every time before the line with AV you do logging like ShowMessage(IntToHex(Cardinal( P ))) - most probably you'd see nil thereCecilla
@DanielChaves 1) you tired that sample on virtual or static setter? 2) you do demo program with TRttiProperty.SetValue on your property. It should find and call your method. If it does - then you just debug side by side that demo and your program and on each step you notice which values were fetched to which variable. And then you find where they act different and fix your program to work like TRttiProperty.SetValue internals.Cecilla
I did it Arioch, and I edited th original post to add the working code for my problem. Thank you very, very much for the tips, and sorry for the mistakes I did in my posts. :DValer
Glad you solved it. However are you sure you don;t have to make checks (or at least asserts) in ShowVirtualSettersNames that the method is really virtual ? you're kinda doing guts surgery to the compiler and my gut feeling is that there is never too many safety checks in tasks like this.Cecilla

© 2022 - 2024 — McMap. All rights reserved.