加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

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

发布时间:2020-12-15 04:34:13 所属栏目:大数据 来源:网络整理
导读:对于一个框架,我编写了一个包装器,它接受任何对象,接口或记录类型来探索其属性或字段.类声明如下: TWrapperT = class private FType : TRttiType; FInstance : Pointer; {...}public constructor Create (var Data : T);end; 在构造函数中,我尝试获取进一步
对于一个框架,我编写了一个包装器,它接受任何对象,接口或记录类型来探索其属性或字段.类声明如下:
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参数的程序,这也没有用.当使用非泛型过程时,一切正常,但为了简化包装器的使用,通用解决方案会很好,因为它适用于对象和记录的方式相同.

亲切的问候,

基督教

解决方法

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

>您可以从Interfaces获得有意义的RTTI.糟糕,您可以从界面类型中获取RTTI.
>接口始终由Delphi对象实现(因此您尝试从支持Delphi对象中提取RTTI).

这两种假设都是错误的.接口是非常简单的VIRTUAL METHOD表,对它们来说非常小.由于接口的定义非常狭窄,因此不可能具有RTTI.当然,除非您实现自己的RTTI变体,否则不应该. LE:接口本身不能像TObject那样携带类型信息,但是如果提供了一个IInterface,TypeOf()运算符可以获得TypeInfo

你的第二个假设也是错误的,但不那么重要.在Delphi世界中,大多数接口都将由Delphi对象实现,除非您从使用其他编程语言编写的DLL中获取接口:Delphi的接口是COM兼容的,因此它的实现可以从任何其他COM兼容语言中使用反之亦然.但是既然我们在这里谈论Delphi XE,你可以使用这种语法以直观和可读的方式为它的实现对象构建一个接口:

TObject := IInterface as TObject;

也就是说,使用as运算符. Delphi XE有时会自动转换此类硬件:

TObject := TObject(IInterface);

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

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

示例:Delphi对象不支持的接口实现

只是为了好玩,这里是一个没有Delphi对象支持的界面的快速演示.由于接口只是指向虚方法表的指针,因此我将构造虚方法表,创建指向它的指针并将指针强制转换为所需的接口类型.假虚拟方法表中的所有方法指针都是使用全局函数和过程实现的.想象一下尝试从我的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.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读