【问题标题】:In Delphi XE3, how can I cast a TVirtualInterface object to its interface, using TypeInfo or RTTI?在 Delphi XE3 中,如何使用 TypeInfo 或 RTTI 将 TVirtualInterface 对象转换为其接口?
【发布时间】:2013-04-09 13:17:31
【问题描述】:

我正在尝试使用 TVirtualInterface。我主要尝试遵循Embarcadero doc wikiNick Hodges' blog 的示例。

但是,我正在尝试做的与标准示例有点不同。

我已尽可能简化以下示例代码来说明我正在尝试做的事情。我省略了明显的验证和错误处理代码。

program VirtualInterfaceTest;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.Generics.Collections,
  System.Rtti,
  System.SysUtils,
  System.TypInfo;

type
  ITestData = interface(IInvokable)
    ['{6042BB6F-F30C-4C07-8D3B-C123CF1FF60F}']
    function  GetComment: string;
    procedure SetComment(const Value: string);
    property  Comment: string read GetComment write SetComment;
  end;

  IMoreData = interface(IInvokable)
    ['{1D2262CE-09F4-45EC-ACD8-3EEE6B2F1548}']
    function  GetSuccess: Boolean;
    procedure SetSuccess(const Value: Boolean);
    property  Success: Boolean read GetSuccess write SetSuccess;
  end;

  TDataHolder = class
  private
    FTestData: ITestData;
    FMoreData: IMoreData;
  public
    property TestData: ITestData read FTestData write FTestData;
    property MoreData: IMoreData read FMoreData write FMoreData;
  end;

  TVirtualData = class(TVirtualInterface)
  private
    FData: TDictionary<string, TValue>;
    procedure DoInvoke(Method: TRttiMethod; 
                       const Args: TArray<TValue>; 
                       out Result: TValue);
  public
    constructor Create(PIID: PTypeInfo);
    destructor Destroy; override;
  end;

constructor TVirtualData.Create(PIID: PTypeInfo);
begin
  inherited Create(PIID, DoInvoke);
  FData := TDictionary<string, TValue>.Create;
end;

destructor TVirtualData.Destroy;
begin
  FData.Free;
  inherited Destroy;
end;

procedure TVirtualData.DoInvoke(Method: TRttiMethod; 
                                const Args: TArray<TValue>; 
                                out Result: TValue);
var
  key: string;
begin
  if (Pos('Get', Method.Name) = 1) then
  begin
    key := Copy(Method.Name, 4, MaxInt);
    FData.TryGetValue(key, Result);
  end;

  if (Pos('Set', Method.Name) = 1) then
  begin
    key := Copy(Method.Name, 4, MaxInt);
    FData.AddOrSetValue(key, Args[1]);
  end;
end;

procedure InstantiateData(obj: TObject);
var
  rttiContext:  TRttiContext;
  rttiType:     TRttiType;
  rttiProperty: TRttiProperty;
  propertyType: PTypeInfo;
  data:         IInterface;
  value:        TValue;
begin
  rttiContext := TRttiContext.Create;
  try
    rttiType := rttiContext.GetType(obj.ClassType);
    for rttiProperty in rttiType.GetProperties do
    begin
      propertyType := rttiProperty.PropertyType.Handle;
      data := TVirtualData.Create(propertyType) as IInterface;
      value := TValue.From<IInterface>(data);
      //  TValueData(value).FTypeInfo := propertyType;
      rttiProperty.SetValue(obj, value);  //  <<====  EInvalidCast
    end;
  finally
    rttiContext.Free;
  end;
end;

procedure Test_UsingDirectInstantiation;
var
  dataHolder: TDataHolder;
begin
  dataHolder := TDataHolder.Create;
  try
    dataHolder.TestData := TVirtualData.Create(TypeInfo(ITestData)) as ITestData;
    dataHolder.MoreData := TVirtualData.Create(TypeInfo(IMoreData)) as IMoreData;

    dataHolder.TestData.Comment := 'Hello World!';
    dataHolder.MoreData.Success := True;

    Writeln('Comment:  ', dataHolder.TestData.Comment);
    Writeln('Success:  ', dataHolder.MoreData.Success);
  finally
    dataHolder.Free;
  end;
end;

procedure Test_UsingIndirectInstantiation;
var
  dataHolder: TDataHolder;
begin
  dataHolder := TDataHolder.Create;
  try
    InstantiateData(dataHolder);  //  <<====

    dataHolder.TestData.Comment := 'Hello World!';
    dataHolder.MoreData.Success := False;

    Writeln('Comment:  ', dataHolder.TestData.Comment);
    Writeln('Success:  ', dataHolder.MoreData.Success);
  finally
    dataHolder.Free;
  end;
end;

begin
  try
    Test_UsingDirectInstantiation;
    Test_UsingIndirectInstantiation;
  except on E: Exception do
    Writeln(E.ClassName, ':  ', E.Message);
  end;
  Readln;
end.

我有一些具有读/写属性的任意接口ITestDataIMoreData,以及一个包含对这些接口的引用的类IDataHolder

按照 Nick Hodges 的示例,我创建了一个类 TVirtualData,它继承自 TVirtualInterface。当我按照我在所有示例中看到的方式使用这个类时,就像在Test_UsingDirectInstantiation 中一样,它的效果非常好。

然而,我的代码需要做的是以更间接的方式实例化接口,如Test_UsingIndirectInstantiation

InstantiateData 方法使用 RTTI,并且在引发 EInvalidCast 异常(“无效类类型转换”)的 SetValue 调用之前运行良好。

我在注释行(我在“Delphi Sorcery”的一些示例代码中看到)添加了尝试将数据对象转换为适当的接口。这允许SetValue 调用干净地运行,但是当我尝试访问接口属性(即dataHolder.TestData.Comment)时,它抛出了一个 EAccessViolation 异常(“地址 00000000 的访问冲突。读取地址 00000000”)。

为了好玩,我将InstantiateData 方法中的IInterface 替换为ITestData,对于第一个属性它工作得很好,但很自然地,它不适用于第二个属性。

问题: 有没有办法使用 TypeInfo 或 RTTI(或其他东西)将这个 TVirtualInterface 对象动态转换为适当的接口,以便 InstantiateData 方法具有与设置相同的效果直接属性?

【问题讨论】:

  • 附注——您不必创建 TRttiContext 的实例——它会在首次使用时自动实例化。
  • 而且你也不需要释放它!
  • 谢谢。很高兴知道这一点。

标签: delphi interface casting rtti delphi-xe3


【解决方案1】:

首先,您必须将实例转换为正确的接口,而不是 IInterface。您仍然可以将它存储在 IInterface 变量中,但它确实包含对正确接口类型的引用。

然后您必须将其放入具有正确类型而不是 IInterface 的 TValue 中(RTTI 对类型非常严格)

您添加的注释行只是为了解决第二个问题,但由于它实际上包含 IInterface 引用(而不是 ITestData 或 TMoreData 引用),它导致了 AV。

procedure InstantiateData(obj: TObject);
var
  rttiContext:  TRttiContext;
  rttiType:     TRttiType;
  rttiProperty: TRttiProperty;
  propertyType: PTypeInfo;
  data:         IInterface;
  value:        TValue;
begin
  rttiType := rttiContext.GetType(obj.ClassType);
  for rttiProperty in rttiType.GetProperties do
  begin
    propertyType := rttiProperty.PropertyType.Handle;
    Supports(TVirtualData.Create(propertyType), TRttiInterfaceType(rttiProperty.PropertyType).GUID, data);
    TValue.Make(@data, rttiProperty.PropertyType.Handle, value);
    rttiProperty.SetValue(obj, value);
  end;
end;

【讨论】:

  • 这绝对解决了我的问题。我应该在这几天前问的。
猜你喜欢
  • 2011-05-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-28
  • 2012-04-03
  • 2010-09-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多