我正在尝试使用TVirtualInterface。我主要尝试按照Embarcadero doc wiki和Nick 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.
我有一些带有读/写属性的ITestData
和IMoreData
的任意接口,以及一个保存对这些接口IDataHolder
的引用的类。
我创建了一个类TVirtualData
,它继承自TVirtualInterface
,遵循Nick Hodges的例子。当我在所有示例中使用此类时,就像在Test_UsingDirectInstantiation
中一样,它会起作用。
然而,我的代码需要做的是以更间接的方式实例化接口,如Test_UsingIndirectInstantiation
。
InstantiateData
方法使用RTTI,并且在调用抛出EInvalidCast异常(“Invalid class typecast”)的SetValue
调用之前一直运行良好。
我在注释行中添加了(我在“Delphi Sorcery”的一些示例代码中看到),试图将数据对象转换为适当的接口。这允许SetValue
调用干净地运行,但是当我尝试访问接口属性(即dataHolder.TestData.Comment
)时,它抛出了EAccessViolation异常(“地址00000000处的访问冲突。读取地址00000000”)。
为了好玩,我将IInterface
方法中的InstantiateData
替换为ITestData
,对于第一个属性,它可以正常工作,但当然,它不适用于第二个属性。< / p>
问题:有没有办法使用TypeInfo或RTTI(或其他东西)将此TVirtualInterface
对象动态转换为适当的接口,以便InstantiateData
方法具有与直接设置属性相同的效果?
答案 0 :(得分:8)
首先,您必须将实例强制转换为正确的接口,而不是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;