I made a small working example of what I'm trying to do, everything works fine but not the part where I want to call a method with Rtti, check the comments in the method DoSomeTask. I guess looking straight to the line that fails is better than an explanation, but I will try. Briefly, I have some nested classes, the main static class TSCSettings with a public property (Users) which is an instance of the class TUsers. TUsers has a field fAccounting which is an instance of the class TSettingsAccounting. The idea is to load users settings in this way:
TSCSettings.Users.Accounting.LoadSettings(UserID);
and it's working fine like that, but in the method DoSomeTask it fails to call the method LoadSettings of the instance fAccounting with Rtti in the line
meth.Invoke(vField, [1]); --> exception class EInvalidCast with message 'Invalid class typecast'.
Thanks a lot for your help.
program Rtti_CallMethods;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
RTTI;
type
TSettingsAccounting = Class(TObject)
public
function LoadSettings(UserID: Integer): Boolean;
end;
TUsers = Class(TObject)
strict private
fAccounting: TSettingsAccounting;
public
constructor Create; virtual;
property Accounting: TSettingsAccounting read fAccounting write fAccounting;
function DoSomeTask: Boolean;
end;
//Static class
TSCSettings = Class(TObject)
strict private
class var fUsers: TUsers;
public
class property Users: TUsers read fUsers write fUsers;
class constructor Create;
end;
{ TSettingsAccounting }
function TSettingsAccounting.LoadSettings(UserID: Integer): Boolean;
begin
Writeln('...Task load settings ' + UserID.ToString);
end;
{ TCLUsers }
constructor TUsers.Create;
begin
fAccounting:= TSettingsAccounting.Create;
end;
function TUsers.DoSomeTask: Boolean;
var
vCtx: TRttiContext;
vType: TRttiType;
vField: TRttiField;
meth : TRttiMethod;
sInfo: string;
begin
//---- fAccounting is already instanciated and calling the method in
//this way works fine, but fails when I try to do the same below with Rtti
fAccounting.LoadSettings(4);
//----
vType := vCtx.GetType(self.ClassType); //--> TCLUsers
for vField in vType.GetFields do
begin
sInfo:= vField.Name; // --> fAccounting
//------ This way works fine
meth := vField.FieldType.GetMethod('LoadSettings');
meth.Invoke(fAccounting, [2]);
//------
//------ This way fails: ... exception class EInvalidCast with message 'Invalid class typecast'.
meth.Invoke(vField, [1]);
//------
end;
end;
class constructor TSCSettings.Create;
begin
fUsers:= TUsers.Create;
end;
// -------------------------------
begin
TSCSettings.Users.Accounting.LoadSettings(3); //This works fine
TSCSettings.Users.DoSomeTask;
readln;
end.
The 1st parameter of TRttiMethod.Invoke()
is a pointer to the object to call the method on. That pointer can be passed in as either a direct TObject
pointer, or as a TValue
containing the TObject
pointer.
In this case, a TSettingsAccounting
object is needed. Which is why meth.Invoke(fAccounting, [2]);
works. But on the failing line, you are giving it a pointer to the TRttiField
itself, which is why meth.Invoke(vField, [1]);
fails. TRttiMethod
derives from TObject
, so the code compiles, but it is the wrong TObject
to pass in.
You need the TObject
from reading the value of the TRttiField
(the value of the fAccounting
field), eg:
meth.Invoke(vField.GetValue(Self), [1]);