I am trying to call the constructor obtained through RTTI (running D2010 version 14.0.3593.25826). The constructor takes as arguments a mixture of strings and objects, all of which must be initialized to ''
or nil
. (Disclaimer: I know that the desired constructor will be the one with the maximum number of parameters, therefore, it looks strange looking, although not optimal design.)
The code is as follows:
program sb_rtti; {$APPTYPE CONSOLE} uses RTTI, TypInfo, SysUtils; type TMyClass = class (TObject) FField1: string; FObject1: TObject; public constructor Create(Field1: string = ''; Object1: TObject = nil); end; constructor TMyClass.Create(Field1: string; Object1: TObject); begin FField1 := Field1; FObject1 := Object1; end; function GetConstructor(rType: TRttiType) : TRttiMethod; var MaxParams: integer; Methods: TArray<TRttiMethod>; Method: TRttiMethod; Params: TArray<TRttiParameter>; begin Methods := rType.GetMethods('Create'); MaxParams := 0; for Method in Methods do begin Params := Method.GetParameters(); if (Length(Params) > MaxParams) then begin Result := Method; MaxParams := Length(Params); end; end; end; procedure InitializeParam(Param: TRttiParameter; ActualParam: TValue); begin if (Param.ParamType.TypeKind = TTypeKind.tkClass) then begin ActualParam := TValue.From<TObject>(nil); end else if (Param.ParamType.TypeKind = TTypeKind.tkString) then begin ActualParam := TValue.From<string>(''); end else if (Param.ParamType.TypeKind = TTypeKind.tkUString) then begin ActualParam := TValue.From<UnicodeString>(''); end else begin // Other types goes here end; end; var Context: TRttiContext; Constr: TRttiMethod; Params: TArray<TRttiParameter>; ResultValue: TValue; rType: TRttiType; ActualParams: array of TValue; i: integer; CurrentParam: TRttiParameter; begin Context := TRttiContext.Create(); rType := Context.GetType(TypeInfo(TMyClass)); Constr := GetConstructor(rType); try if (Constr <> nil) then begin Params := Constr.GetParameters(); SetLength(ActualParams, Length(Params)); for i := 0 to Length(Params) - 1 do begin CurrentParam := Params[i] as TRttiParameter; InitializeParam(CurrentParam, ActualParams[i]); end; ResultValue := Constr.Invoke(rType.AsInstance.MetaclassType, ActualParams); end; except on E : Exception do WriteLn(E.ToString); end; ReadLn; end.
Now when the line ResultValue := Constr.Invoke(rType.AsInstance.MetaclassType, ActualParams);
is ResultValue := Constr.Invoke(rType.AsInstance.MetaclassType, ActualParams);
, an EInvalidCast exception is thrown. An exception can be attributed to the TValue.Cast
method on line 1336.
However, the problem meat is similar to the previous point in the call stack, more precisely on line 4093 in rtti.pas ( argList[currArg] := Args[i].Cast(parList[i].ParamType.Handle);
).
My bet is that I use rtti in ways I shouldn't, but I can't find the โright pathโ described anywhere. Can someone point me in the right direction? Thanks!
source share