Delphi: constructor call calls EInvalidCast

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!

+4
source share
2 answers

You have a problem in the InitializeParam procedure, because when you assign the ActualParam parameter ActualParam you set the value of the local copy of this parameter - remember that TValue (type ActualParam ) is a record. Therefore, to fix the problem, you must pass ActualParam as the var parameter.

 procedure InitializeParam(Param: TRttiParameter; var ActualParam: TValue); 
+5
source

It just occurred to me to hard code the initialization of the argument, replacing

 for i := 0 to Length(Params) - 1 do begin CurrentParam := Params[i] as TRttiParameter; InitializeParam(CurrentParam, ActualParams[i]); end; 

with

 ActualParams[0] := TValue.From<string>(''); ActualParams[1] := TValue.From<TObject>(nil); 

which solves the problem.

0
source

All Articles