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.

有帮助吗?

解决方案

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?

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top