RTTI information for method pointer
Asked Answered
S

1

4

Is it possible to obtain RTTI information about a TMethod?

I can get the instance by

Instance := TObject(Method.Data);

so I can get the RTTI type of the instance, but how can I get the correct TRttiMethod? I want to check for attributes on a method passed in using a method pointer.

Sierra answered 5/2, 2013 at 10:16 Comment(0)
P
4

This approach works in theory, and there's a good change it will work in practice, but there are a couple of things that could prevent you from getting hold of the TRttiMethod.

  • The TMethod record says Data: Pointer, not TObject. This implies there might be a possibility of having something other then an TObject as the Data! This is a serious issue, because if the Data is not TObject, then attempting to extract RTTI from it is going to result in runtime errors.
  • Not all methods have RTTI. By default methods in the private area do not have RTTI, and one can use the {$RTTI} to stop generating RTTI for public or published members as well.

Those two issues would not be a problem for the usual type of event implementations we have in Delphi (double-click on the name of the event in Object Inspector and fill in the code), but then again I don't think you're talking about "vanila" implementations. Not many people would decorate the default event handlers with Attributes!

Code that demonstrates all of the above:

program Project15;

{$APPTYPE CONSOLE}

uses
  SysUtils, RTTI;

type
  // Closure/Event type
  TEventType = procedure of object;

  // An object that has a method compatible with the declaration above
  TImplementation = class
  private
    procedure PrivateImplementation;
  public
    procedure HasRtti;

    procedure GetPrivateImpEvent(out Ev:TEventType);
  end;

  TRecord = record
    procedure RecordProc;
  end;

  // an object that has a compatible method but provides no RTTI
  {$RTTI EXPLICIT METHODS([])}
  TNoRttiImplementation = class
  public
    procedure NoRttiAvailable;
  end;

procedure TImplementation.GetPrivateImpEvent(out Ev:TEventType);
begin
  Ev := PrivateImplementation;
end;

procedure TImplementation.HasRtti;
begin
  WriteLn('HasRtti');
end;

procedure TNoRttiImplementation.NoRttiAvailable;
begin
  WriteLn('No RTTI Available');
end;

procedure TRecord.RecordProc;
begin
  WriteLn('This is written from TRecord.RecordProc');
end;

procedure TImplementation.PrivateImplementation;
begin
  WriteLn('PrivateImplementation');
end;

procedure TotalyFakeImplementation(Instance:Pointer);
begin
  WriteLn('Totaly fake implementation, TMethod.Data is nil');
end;

procedure SomethingAboutMethod(X: TEventType);
var Ctx: TRttiContext;
    Typ: TRttiType;
    Method: TRttiMethod;
    Found: Boolean;
begin
  WriteLn('Invoke the method to prove it works:');
  X;
  // Try extract information about the event
  Ctx := TRttiContext.Create;
  try
    Typ := Ctx.GetType(TObject(TMethod(X).Data).ClassType);
    Found := False;
    for Method in Typ.GetMethods do
      if Method.CodeAddress = TMethod(X).Code then
      begin
        // Got the Method!
        WriteLn('Found method: ' + Typ.Name + '.' + Method.Name);
        Found := True;
      end;
    if not Found then
      WriteLn('Method not found.');
  finally Ctx.Free;
  end;
end;

var Ev: TEventType;
    R: TRecord;

begin
  try
    try
      WriteLn('First test, using a method that has RTTI available:');
      SomethingAboutMethod(TImplementation.Create.HasRtti);
      WriteLn;

      WriteLn('Second test, using a method that has NO rtti available:');
      SomethingAboutMethod(TNoRttiImplementation.Create.NoRttiAvailable);
      WriteLn;

      WriteLn('Third test, private method, default settings:');
      TImplementation.Create.GetPrivateImpEvent(Ev);
      SomethingAboutMethod(Ev);
      WriteLn;

      WriteLn('Assign event handler using handler from a record');
      try
        SomethingAboutMethod(R.RecordProc);
      except on E:Exception do WriteLn(E.Message);
      end;
      WriteLn;

      WriteLn('Assign event handler using static procedure');
      try
        TMethod(Ev).Data := nil;
        TMethod(Ev).Code := @TotalyFakeImplementation;
        SomethingAboutMethod(Ev);
      except on E:Exception do WriteLn(E.Message);
      end;
      WriteLn;

    except
      on E: Exception do Writeln(E.ClassName, ': ', E.Message);
    end;
  finally ReadLn;
  end;
end.
Polysyllable answered 5/2, 2013 at 10:36 Comment(6)
CodeAddress seems like exactly what I wanted...I'll try it and give feedback later.Sierra
Your first concern is not an issue for me, because I take procedure of object which I then convert to TMethod to store it, so there is always an object instance behind the TMethod. Second concern could indeed be an issue.Sierra
@Smasher, it's a serious concern; The procedure of object is just syntax, you can assign a record's method to the event handler just fine. No hacks, no compiler warnings. I'll update the code yet again to include two cases where it would generate runtime errors.Polysyllable
I've included a couple more examples: implementing the event handler using a Record and implementing the event handler using a static procedure (ie: not a method of class or record). Both are fine as far as Delphi is concerned, both generate runtime error if you make assumptions about TMethod.DataPolysyllable
@Cosmin, pointers to static methods are not allowed in method pointers. Method pointers can be pointers to instance methods or class methods, or apparently record methods. Assigning a pointer to a static method is an error, though. The compiler should flag it. (If you're using the @ operator in the assignment, you're doing it wrong.) The TMethod.Data member will be either the TObject or TClass reference (or apparently a record pointer). At design time, it can hold whatever bookkeeping information the IDE chooses for event handlers.Cape
@RobKennedy, the idea was that the TMethod structure contains two untyped Pointers; If all you've got is the TMethod structure you can't make assumptions, that's all. Sure, I "filled" the TEventType structure using a hard-cast to TMethod and then used the @ operator. The result is a perfectly valid TEventType that can be used just like any other TEventType; As long as you don't mess with it's internals.Polysyllable

© 2022 - 2024 — McMap. All rights reserved.