Delphi:调用构造函数引发 EInvalidCast

发布于 2024-12-11 13:13:51 字数 2760 浏览 0 评论 0原文

我试图调用通过 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 中的第 4093 行处.pas (argList[currArg] := Args[i].Cast(parList[i].ParamType.Handle);)。

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

I'm trying to invoke a constructor obtained via RTTI (running D2010 version 14.0.3593.25826). The constructor takes a mixture of strings and objects as its arguments, all of which should be initialized to '' or nil. (Disclaimer: I know that the desired constructor will be the one with maximum number of parameters, hence the weird-looking, although suboptimal design.)

The code goes 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 executed, an EInvalidCast exception is raised. The exception may be traced to the TValue.Cast-method at line 1336.

However, the meat of the problem seems to be found at the previous point in the call stack, more precisely at line 4093 in rtti.pas (argList[currArg] := Args[i].Cast(parList[i].ParamType.Handle);).

My bet is that I'm using rtti in ways I'm not supposed to, yet, I can't find the "right way" described anywhere. Can anybody please point me in the right direction? Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

离不开的别离 2024-12-18 13:13:51

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

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

You have a problem in the InitializeParam procedure because in the assignment of the ActualParam parameter, you are setting the value of the local copy of that parameter – remember that TValue (the type of ActualParam) is a record. So to fix the problem you must pass the ActualParam as a var parameter.

procedure InitializeParam(Param: TRttiParameter; var ActualParam: TValue);
箹锭⒈辈孓 2024-12-18 13:13:51

我只是想到通过替换为来硬编码参数初始化

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

解决问题。

It just occurred to me to hard-code the argument initialization by 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文