【问题标题】:Delphi: invoke constructor raises EInvalidCastDelphi:调用构造函数引发 EInvalidCast
【发布时间】:2011-10-21 08:24:03
【问题描述】:

我正在尝试调用通过 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 异常。异常可以追溯到第 1336 行的TValue.Cast-方法。

但是,问题的关键似乎在调用堆栈的前一点,更准确地说是在 rtti.pas (argList[currArg] := Args[i].Cast(parList[i].ParamType.Handle);) 的第 4093 行。

我敢打赌,我以不应该的方式使用 rtti,但是,我在任何地方都找不到描述的“正确方式”。有人可以指出我正确的方向吗?谢谢!

【问题讨论】:

    标签: delphi delphi-2010 rtti


    【解决方案1】:

    InitializeParam 过程中存在问题,因为在分配 ActualParam 参数时,您正在设置该参数的本地副本的值 - 请记住 TValueActualParam 的类型)是一个记录。因此,要解决此问题,您必须将 ActualParam 作为 var 参数传递。

    procedure InitializeParam(Param: TRttiParameter; var ActualParam: TValue);
    

    【讨论】:

    • 谢谢伙计,这让我恍然大悟……我一直在混淆按引用和按值传递的规则。 :) 我想我的思想是如此固定在“它必须与 rtti 相关”的想法上,以至于我忘记了基础知识。
    【解决方案2】:

    我突然想到通过替换来硬编码参数初始化

    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);
    

    这解决了问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-16
      • 2017-01-21
      • 1970-01-01
      • 1970-01-01
      • 2017-09-04
      • 1970-01-01
      相关资源
      最近更新 更多