Delphi Class Helper RTTI GetMethod
Asked Answered
D

1

8

Lets say I have a sample class helper

TSampleClassHelper = class helper for TSampleClass
public
  procedure SomeHelper;
end;

I do the following:

var
  obj :TSampleClass;
begin
  obj:=TSampleClass.Create;
  obj.SomeHelper;
end;

and this works as expected.

But how can I use RTTI to invoke the helper method instead? The following does not seem to work, GetMethod returns nil.

var
  obj :TSampleClass;
  ctx :TRTTIContext;
  rtype :TRTTIType;
  rmethod :TRTTIMethod;
begin
  obj:=TSampleClass.Create;
  rtype:=ctx.GetType(obj.ClassType);
  rmethod:=rtype.GetMethod('SomeHelper'); // rmethod is nil !
end;

So does RTTI not work for methods defined in class helpers? Is there anyway around this?

Thanks.

Dispassionate answered 21/6, 2013 at 19:57 Comment(1)
I see, but in my real life code I am testing for 'SomeMethod' against any arbitrary object. I do not know if the object has the method defined via a helper or not. So I guess it won't work for 'SomeMethod' defined via a class helper. Oh well.Dispassionate
P
10

The reason your code returns a nil method is that the object's type does not contain a method named SomeHelper. The type that contains that method is the helper type.

So, you could write this which will return a non-nil method:

obj:=TSampleClass.Create;
rtype:=ctx.GetType(TypeInfo(TSampleClassHelper));
rmethod:=rtype.GetMethod('SomeHelper');

Of course, you should immediately see the first problem, namely the use of a compile time specified type, TSampleClassHelper. Can we use RTTI to discover TSampleClassHelper at run time based on the type of the instance? No we cannot, as I will explain below.

Even if we put that to one side, as far as I can see, there's no way to invoke the method using RTTI. If you call rmethod.Invoke(obj, []) then the code in TRttiInstanceMethodEx.DispatchInvoke blocks an attempt to call the helper method. It blocks it because it decrees that the type of the instance is not compatible with the class of the method. The pertinent code is:

if (cls <> nil) and not cls.InheritsFrom(TRttiInstanceType(Parent).MetaclassType) then
  raise EInvalidCast.CreateRes(@SInvalidCast);

Well, you can obtain the code address of the helper method with rmethod.CodeAddress but you'll need to find some other way to invoke that method. It's easy enough to cast it to a method with the appropriate signature and invoke it. But why bother with rmethod.CodeAddress in any case? Why not use TSomeHelperClass.SomeMethod and cut RTTI out of the loop?

Discussion

Helper resolution is performed statically based on the active helper at the point of compilation. Once you attempt to invoke a helper method using RTTI there is no active helper. You've long since finished compiling. So you have to decide which helper class to use. At which point, you don't need RTTI.

The fundamental issue here is that class helper method resolution is fundamentally a static process performed using the context of the compiler. Since there is not compiler context at run time, class helper method resolution cannot be performed using RTTI.

For more insight into this have a read of Allen Bauer's answer here: Find all Class Helpers in Delphi at runtime using RTTI?

Puritan answered 21/6, 2013 at 20:16 Comment(2)
It is not that there are multiple helpers for a class. It is that some classes have the required method defined by a helper, some do not. For example for TStrings I have added the method via a helper but for TMyClass it is defined directly on the class.Dispassionate
@Dispassionate The real issue is that helper method resolution is performed statically by the compiler based on compilation context. That concept is completely orthogonal to RTTI.Puritan

© 2022 - 2024 — McMap. All rights reserved.