Delphi: вызывает конструктор поднимает EInvalidCast - PullRequest
4 голосов
/ 21 октября 2011

Я пытаюсь вызвать конструктор, полученный через RTTI (работает версия D2010 14.0.3593.25826).Конструктор принимает в качестве аргументов смесь строк и объектов, каждый из которых должен быть инициализирован как '' или nil.(Отказ от ответственности: я знаю, что желаемый конструктор будет с максимальным количеством параметров, следовательно, выглядит странно, хотя и неоптимально.)

Код выглядит следующим образом:

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.

Теперь, когда строка ResultValue := Constr.Invoke(rType.AsInstance.MetaclassType, ActualParams); выполнена, возникает исключение EInvalidCast.Исключение можно проследить до TValue.Cast -метода в строке 1336.

Однако, суть проблемы, кажется, находится в предыдущей точке стека вызовов, точнее в строке 4093 в rtti.pas (argList[currArg] := Args[i].Cast(parList[i].ParamType.Handle);).

Могу поспорить, что я использую rtti способами, которые я не должен, но я не могу найти «правильный путь», описанный где-либо.Кто-нибудь может указать мне правильное направление?Спасибо!

Ответы [ 2 ]

5 голосов
/ 21 октября 2011

У вас проблема в процедуре InitializeParam, поскольку при присвоении параметра ActualParam вы устанавливаете значение локальной копии этого параметра - помните, что TValue (тип ActualParam) это запись. Поэтому, чтобы решить проблему, вы должны передать ActualParam в качестве параметра var.

procedure InitializeParam(Param: TRttiParameter; var ActualParam: TValue);
0 голосов
/ 21 октября 2011

Мне пришло в голову жестко закодировать инициализацию аргумента, заменив

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

с

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

которая решает проблему.

...