对于框架我编写了一个包装器,它使用任何对象,接口或记录类型来探索其属性或字段。类声明如下:
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参数的程序,这也没有用。使用非泛型过程时一切正常,但为了简化包装器的使用,通用解决方案会很好,因为它适用于对象和记录。
亲切的问候,
基督教
答案 0 :(得分:8)
您的代码包含两个错误的假设:
RTTI
。两种假设都是错误的。接口是非常简单的VIRTUAL METHOD表,对它们来说非常小。 由于接口的定义非常狭窄,因此不可能有 LE:接口本身不能像TObject那样携带类型信息,但是TypeOf()如果提供了IInterface RTTI
。当然,除非您实现自己的RTTI
变体,否则不应该。
你的第二个假设也是错误的,但不那么重要。在Delphi世界中,大多数接口都将由Delphi对象实现,除非您从使用其他编程语言编写的DLL中获取接口:Delphi的接口与COM兼容,因此它的实现可以从任何其他COM兼容语言中使用反之亦然。但是既然我们在这里讨论Delphi XE,你可以使用这种语法以直观和可读的方式为它的实现对象构建一个接口:
TObject := IInterface as TObject;
即使用as
运算符。 Delphi XE有时会自动转换此类硬件:
TObject := TObject(IInterface);
提到"as"
语法,但我不喜欢这种魔法,因为它看起来非常反直觉,并且在旧版本的Delphi中表现不同。
从另一个角度来看,将Interface
强制转换回它的实现对象也是错误的:它会显示 all 实现对象的属性,而不仅仅是与接口相关的属性,以及这是非常错误的,因为你首先使用接口来隐藏这些实现细节!
只是为了好玩,这里是一个由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.
答案 1 :(得分:2)
两个可能的答案。
如果这种情况总是发生,即使T
是一个对象,那么这是一个编译器错误,你应该提交一份关于它的QC报告。 (使用Interfaces,cast-an-interface-to-object对象需要编译器的一些黑魔法,并且泛型子系统可能无法正确实现它。)
如果您正在使用不是对象的T
,例如记录类型,并且出现此错误,那么一切都按设计工作;你只是不正确地使用类型转换。
无论哪种方式,都有办法从任意类型中获取RTTI信息。你知道TRttiContext.GetType有两个重载吗?使用另一个。请尝试GetType (TObject (Data).ClassInfo)
。
calling GetType(TypeInfo(Data))
哦,并将FInstance声明为T
而不是pointer
。它会为你省去很多麻烦。