Delphi Rtti 用于通用上下文中的接口(interface)

标签 delphi generics interface rtti

对于一个框架,我编写了一个包装器,它采用任何对象、接口(interface)或记录类型来探索其属性或字段。类声明如下:

TWrapper<T> = class 
private
  FType : TRttiType;
  FInstance : Pointer;
  {...}
public
  constructor Create (var Data : T);
end;

在构造函数中,我尝试获取类型信息以进行进一步的处理步骤。

constructor TWrapper<T>.Create (var Data : T);
begin
FType := RttiCtx.GetType (TypeInfo (T));
if FType.TypeKind = tkClass then
  FInstance := TObject (Data)
else if FType.TypeKind = tkRecord then
  FInstance := @Data
else if FType.TypeKind = tkInterface then
  begin
  FType := RttiCtx.GetType (TObject (Data).ClassInfo); //<---access violation
  FInstance := TObject (Data);
  end
else
  raise Exception.Create ('Unsupported type');
end;

我想知道这个访问冲突是否是delphi编译器中的一个错误(我使用的是XE)。 经过进一步调查,我编写了一个简单的测试函数,它表明,询问类名也会产生此异常:

procedure TestForm.FormShow (Sender : TObject);
var
  TestIntf : IInterface;
begin
TestIntf    := TInterfacedObject.Create;
OutputDebugString(PChar (TObject (TestIntf).ClassName)); //Output: TInterfacedObject
Test <IInterface> (TestIntf);
end;

procedure TestForm.Test <T> (var Data : T);
begin
OutputDebugString(PChar (TObject (Data).ClassName)); //access violation
end;

谁能给我解释一下,这是怎么回事?我还尝试了没有 var 参数的过程,但也不起作用。当使用非通用过程时,一切正常,但为了简化包装器的使用,通用解决方案会很好,因为它以相同的方式适用于对象和记录。

亲切的问候,

基督教

最佳答案

您的代码包含两个错误的假设:

  • 您可以从接口(interface)获取有意义的RTTI哎呀,您可以从接口(interface)类型获取RTTI。
  • 接口(interface)始终由 Delphi 对象实现(因此您尝试从支持的 Delphi 对象中提取 RTTI)。

这两种假设都是错误的。接口(interface)是非常简单的虚拟方法表,对它们来说没有什么魔力。 由于接口(interface)的定义如此狭窄,因此它不可能有RTTI。当然,除非您实现自己的 RTTI 变体,但您不应该这样做。 LE:接口(interface)本身不能像 TObject 那样携带类型信息,但是如果提供了 IInterface,TypeOf() 运算符可以获得 TypeInfo

你的第二个假设也是错误的,但不是那么错误。在 Delphi 世界中,大多数接口(interface)将由 Delphi 对象实现,当然,除非您从用其他编程语言编写的 DLL 获取接口(interface):Delphi 的接口(interface)是 COM 兼容的,因此它的实现可以从任何其他 COM 兼容的语言使用反之亦然。但由于我们在这里讨论的是 Delphi XE,因此您可以使用此语法以直观且可读的方式将接口(interface)转换为它的实现对象:

TObject := IInterface as TObject;

即使用as运算符。 Delphi XE 有时会自动转换这种类型的硬转换:

TObject := TObject(IInterface);

提到的“as”语法,但我不喜欢这个魔法,因为它看起来非常违反直觉,并且在旧版本的Delphi中表现不同。

从另一个角度来看,将Interface转换回其实现对象也是错误的:它将显示实现对象的所有属性,而不仅仅是那些与接口(interface),这是非常错误的,因为您首先使用接口(interface)来隐藏这些实现细节!

示例:Delphi 对象不支持接口(interface)实现

只是为了好玩,这里有一个由 Delphi 对象支持的界面的快速演示。由于接口(interface)只不过是指向虚拟方法表的指针,因此我将构造虚拟方法表,创建指向它的指针并将该指针强制转换为所需的接口(interface)类型。我的假虚拟方法表中的所有方法指针都是使用全局函数和过程实现的。想象一下尝试从我的 i2 界面中提取 RTTI!

program Project26;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type

  // This is the interface I will implement without using TObject
  ITestInterface = interface
  ['{CFC4942D-D8A3-4C81-BB5C-6127B569433A}']
    procedure WriteYourName;
  end;

  // This is a sample, sane implementation of the interface using an
  // TInterfacedObject method
  TSaneImplementation = class(TInterfacedObject, ITestInterface)
  public
    procedure WriteYourName;
  end;

  // I'll use this record to construct the Virtual Method Table. I could use a simple
  // array, but selected to use the record to make it easier to see. In other words,
  // the record is only used for grouping.
  TAbnormalImplementation_VMT = record
    QueryInterface: Pointer;
    AddRef: Pointer;
    ReleaseRef: Pointer;
    WriteYourName: Pointer;
  end;

// This is the object-based implementation of WriteYourName
procedure TSaneImplementation.WriteYourName;
begin
  Writeln('I am the sane interface implementation');
end;

// This will implement QueryInterfce for my fake IInterface implementation. All the code does
// is say the requested interface is not supported!
function FakeQueryInterface(const Self:Pointer; const IID: TGUID; out Obj): HResult; stdcall;
begin
  Result := S_FALSE;      
end;

// This will handle reference counting for my interface. I am not using true reference counting
// since there is no memory to be freed, si I am simply returning -1
function DummyRefCounting(const Self:Pointer): Integer; stdcall;
begin
  Result := -1;
end;

// This is the implementation of WriteYourName for my fake interface.
procedure FakeWriteYourName(const Self:Pointer);
begin
  WriteLn('I am the very FAKE interface implementation');
end;

var i1, i2: ITestInterface;
    R: TAbnormalImplementation_VMT;
    PR: Pointer;

begin
  // Instantiate the sane implementation
  i1 := TSaneImplementation.Create;

  // Instantiate the very wrong implementation
  R.QueryInterface := @FakeQueryInterface;
  R.AddRef := @DummyRefCounting;
  R.ReleaseRef := @DummyRefCounting;
  R.WriteYourName := @FakeWriteYourName;
  PR := @R;
  i2 := ITestInterface(@PR);

  // As far as all the code using ITestInterface is concerned, there is no difference
  // between "i1" and "i2": they are just two interface implementations.
  i1.WriteYourName; // Calls the sane implementation
  i2.WriteYourName; // Calls my special implementation of the interface

  WriteLn('Press ENTER to EXIT');
  ReadLn;
end.

关于Delphi Rtti 用于通用上下文中的接口(interface),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6278381/

相关文章:

delphi - Delphi 7 和 Delphi XE 及更高版本之间接口(interface)的不同行为

Delphi 操作管理器快捷键 +,-, enter 操作

delphi - 如何将网页下载到变量中?

delphi - Delphi 中可以很好地缩放图像吗?

scala - 在特征之间共享泛型

java - 纯英文界面

delphi - Delphi TClientSocket(仍然)是否已弃用?

c# - 类型可以解析字符串的通用类

java - 是否可以返回 Java 中通用 vector 中元素的副本而不是对其的引用?

java - 远程接口(interface)的 UML 类图