对于一个框架,我编写了一个包装器,它采用任何对象、接口(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)获取有意义的哎呀,您可以从接口(interface)类型获取RTTI。RTTI
。- 接口(interface)始终由 Delphi 对象实现(因此您尝试从支持的 Delphi 对象中提取 RTTI)。
这两种假设都是错误的。接口(interface)是非常简单的虚拟方法表,对它们来说没有什么魔力。 由于接口(interface)的定义如此狭窄,因此它不可能有 LE:接口(interface)本身不能像 TObject 那样携带类型信息,但是如果提供了 IInterface,TypeOf() 运算符可以获得 TypeInfoRTTI
。当然,除非您实现自己的 RTTI
变体,但您不应该这样做。
你的第二个假设也是错误的,但不是那么错误。在 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/