【发布时间】:2015-08-20 09:01:57
【问题描述】:
我写了一个简单的控制台程序来施展一些 RTTI 魔法:
program TypeCast;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.RTTI, Generics.Collections;
type
TSpr = class
public
s: string;
i: Integer;
b: Boolean;
end;
var
Spr: TSpr;
vCtx: TRTTIContext;
vType: TRTTIType;
vField: TRTTIField;
Dict: TDictionary<string, TRTTIField>;
begin
try
Spr := TSpr.Create;
vType := vCtx.GetType(TSpr.ClassInfo);
Dict := TDictionary<string, TRTTIField>.Create;
for vField in vType.GetFields do
Dict.AddOrSetValue(vField.Name, vField);
Dict['s'].SetValue(Spr, 'Hello World!');
Dict['i'].SetValue(Spr, 123);
Dict['b'].SetValue(Spr, True);
Writeln(Spr.s);
Writeln(Spr.i);
Writeln(Spr.b);
Spr.Free;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
输出:
Hello World! 123 TRUE
如果我编译并运行这个程序,它工作正常。但是如果我使用这种技术将这些类型的变量转发到在另一个包中定义的对象,它会给我带来很多麻烦。
MCVE 内容/重现步骤:
...假设您是从空环境开始...
-
创建
PluginInterface包。在那里添加UClassManagerunit UClassManager; interface uses Classes, Contnrs; type TClassManager = class(TClassList); function ClassManager: TClassManager; implementation var Manager: TClassManager; function ClassManager: TClassManager; begin Result := Manager; end; initialization Manager := TClassManager.Create; finalization Manager.Free; end.和
UPlugin单位。unit UPlugin; interface uses RTTI; type TPlugin = class public procedure Init; virtual; abstract; function SetProp(Key: string; Value: TValue): Boolean; virtual; abstract; end; TPluginClass = class of TPlugin; IPluginHost = interface function RunPlugin(PluginName: string): TPlugin; // Run Plugin by it's ClassName end; var Host: IPluginHost; implementation end. -
创建
VCL Forms Application,启用运行时包,添加对PluginInterface的引用并在其上添加TButton。为相应的事件制作这些处理程序:procedure TForm1.FormCreate(Sender: TObject); begin LoadPackage('UniversalSpr.bpl'); Host := Self; end; procedure TForm1.Button1Click(Sender: TObject); var Plugin: TPlugin; begin Plugin := Host.RunPlugin('TSprPlugin'); Plugin.Init; Plugin.SetProp('SprTableName', 'MATERIALS'); Plugin.SetProp('EditRights', True); Plugin.SetProp('BeforePostValue1', 3); end; function TForm1.RunPlugin(PluginName: string): TPlugin; var I: Integer; begin Result := nil; for I := 0 to ClassManager.Count - 1 do if ClassManager[I].ClassNameIs(PluginName) then begin Result := TPluginClass(ClassManager[I]).Create; Break; end; end;粗略地说,
TForm1应该是IPluginHost的后代。并且不要忘记将UClassManager和UPlugin添加到uses子句中。其他单元将由 IDE 自动添加。 -
创建包
UniversalSpr并将其输出文件放置到您的应用程序所在的同一目录中。在TSprPlugin中实现UPlugin:unit USprPlugin; interface uses UPlugin, RTTI, Generics.Collections; type TSpr = class SprTableName: string; BeforePostValue1: int64; EditRights: boolean; end; TSprPlugin = class(TPlugin) procedure Init; override; function SetProp(Key: string; Value: TValue): Boolean; override; private Spr: TSpr; PropDict: TDictionary<string, TRTTIField>; end; implementation procedure TSprPlugin.Init; var vCtx: TRTTIContext; vType: TRTTIType; vField: TRTTIField; begin if not Assigned(Spr) then Spr := TSpr.Create; vType := vCtx.GetType(Spr.ClassInfo); if not Assigned(PropDict) then PropDict := TDictionary<string, TRTTIField>.Create; for vField in vType.GetFields do PropDict.Add(vField.Name, vField); end; function TSprPlugin.SetProp(Key: string; Value: TValue): Boolean; var Field: TRTTIField; begin Result := PropDict.TryGetValue(Key, Field); if Result then // here I get Access Violation Field.SetValue(Spr, Value); end; end.
单击Button1 后,您可以将指定的值传递给属性设置器/修改器,但如果您尝试在例程中重复我的TypeCast 技巧,您将在尝试访问00000004 时遇到访问冲突。
此外,调查和高级调试表明 Field.FieldType 评估正确(这解释了为什么不抛出 InsufficientRTTI),但如果我想得到 Field.Fieldtype.Handle,我会得到臭名昭著的 AV。
我可以设置从原始SetValue 方法中跳过Cast 的值:
function TSprPlugin.SetProp(Key: string; Value: TValue): Boolean;
var
Field: TRTTIField;
begin
Result := PropDict.TryGetValue(Key, Field);
if Result then
Value.ExtractRawData(PByte(Spr) + Field.Offset);
end;
Dalija 建议避免使用包,我已经考虑到这一点,这就是我创建TypeCast 来测试 RTTI 的原因。但是我需要包,因为我的应用程序的设计,我不能只是将它重写为单一的。我可以做些什么来避免这种访问冲突不放弃包?
【问题讨论】:
-
这里不清楚的是包的排列方式。我希望任何对不同包中定义的类型执行 RTTI 的尝试都会失败。您是否考虑过进行一些调试以确定哪一行代码失败,以及哪个引用是
nil? -
顺便说一句,你不需要写
vCtx := TRTTIContext.Create,你可以删除那行。TRttiContext变量会自动初始化。而且你不需要vCtx.Free,你可以删除那行。 -
嗯,我声称
USprPlugin和UniversalSprUnit使用相同的包,它是UniversalSpr。并且通过调试发现可以读取PropDict[Key]的属性,除了FieldType。 -
另一个顺便说一句,不要测试
ContainsKey然后GetValue。这执行两个查找。与TryGetValue合二为一。 -
你删除了一些不相关的东西,但这远非正确的 MCVE。你没有创建最小的例子,还有很多垃圾,但最重要的是你没有创建完整和可验证的例子。如果不进行大量猜测,我无法获取您的代码并按原样编译它。说
ClassManager是简单的类列表并没有削减它。你如何填充它,你如何初始化主机,你如何从它的名字中找到插件。这就是您的真正问题可能隐藏的所有相关信息。
标签: delphi package delphi-xe2 rtti