Delphi 如果在包内访问RTTI,则访问冲突
我编写了一个简单的控制台程序来施展一些RTTI魔法:Delphi 如果在包内访问RTTI,则访问冲突,delphi,package,delphi-xe2,rtti,Delphi,Package,Delphi Xe2,Rtti,我编写了一个简单的控制台程序来施展一些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: TRT
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.
如果我编译并运行这个程序,它可以正常工作。但如果我使用这种技术将这些类型的变量转发到另一个包中定义的对象,这会给我带来很多麻烦
MCVE素材/复制步骤:…假设您从空环境开始
PluginInterface
包。在那里添加UClassManager
unit 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表单应用程序
,启用运行时包,添加对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.
Dalija建议避免使用包,我已经考虑到了这一点,这就是为什么我创建了
TypeCast
来测试RTTI。但我需要软件包,因为我的应用程序的设计,我不能仅仅将其重写为单一的。在不放弃软件包的情况下,如何避免这种访问冲突?无论是否使用运行时软件包,您当前的代码都存在一些问题。您的MCVE并不是完全最小的,而且您已经从正在工作的控制台应用程序向打包的代码中添加了太多无法工作的步骤
在调试您的问题时,您应该先将逻辑封装到TSprPlugin
类中,然后直接测试该类,而不会弄乱运行时包。当您确定TSprPlugin
code功能正常时,您可以添加软件包并查看其运行情况
现在,您的代码在以下简单测试项目中失败
program test;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
USprPlugin in 'USprPlugin.pas',
UPlugin in 'UPlugin.pas';
var
Plugin: TPlugin;
begin
Plugin := TSprPlugin.Create;
Plugin.Init;
Plugin.SetProp('SprTableName', 'MATERIALS');
Plugin.SetProp('EditRights', True);
Plugin.SetProp('BeforePostValue1', 3);
end.
将vCtx
从局部变量移动到TSprPlugin
字段解决了上述测试用例的问题
unit USprPlugin;
interface
uses
UPlugin, RTTI, UniversalSprUnit, Generics.Collections;
type
TSprPlugin = class(TPlugin)
vCtx: TRTTIContext;
procedure Init; override;
function SetProp(Key: string; Value: TValue): Boolean; override;
private
Spr: TSpr;
PropDict: TDictionary<string, TRTTIField>;
end;
implementation
procedure TSprPlugin.Init;
var
vType: TRTTIType;
vField: TRTTIField;
begin
vCtx := TRttiContext.Create;
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.
单位USprPlugin;
接口
使用
UPlugin、RTTI、UniversalSprUnit、泛型药。收藏;
类型
TSprPlugin=class(TPlugin)
vCtx:trtti上下文;
程序初始化;推翻
函数SetProp(键:字符串;值:TValue):布尔;推翻
私有的
Spr:TSpr;
本题:t词典;
结束;
实施
程序TSprPlugin.Init;
变量
v型:trtti型;
vField:TRTTIField;
开始
vCtx:=TRttiContext.Create;
如果未分配(Spr),则
Spr:=TSpr.Create;
vType:=vCtx.GetType(Spr.ClassInfo);
如果未分配(PropDict),则
PropDict:=TDictionary.Create;
对于vType.GetFields中的vField
添加(vField.Name,vField);
结束;
函数TSprPlugin.SetProp(键:字符串;值:TValue):布尔;
变量
字段:TRTTIField;
开始
结果:=PropDict.TryGetValue(键,字段);
如果结果是这样的话
//这里我得到了访问权限冲突
字段设置值(Spr,值);
结束;
结束。
从这里开始,您可以一步一步地添加其他功能,确保每一步都不会破坏功能
此外,您没有释放
Spr
和PropDict
字段,因此造成内存泄漏,但我不确定是否因为该代码与您所遇到的问题没有直接联系,或者您确实存在内存泄漏而未包含该代码。从中不清楚的是软件包的排列方式。我希望任何对不同包中定义的类型执行RTTI的尝试都会失败。您是否考虑过进行一些调试以确定哪一行代码失败,以及哪一引用是nil
?顺便说一句,您不需要编写vCtx:=TRTTIContext。创建,您可以删除该行。TRttiContext
变量将自动初始化。您不需要vCtx.Free
,您可以删除该行。嗯,我声明USprPlugin
和UniversalSprUnit
使用相同的包,它是UniversalSpr
。通过调试,我发现我可以读取PropDict[Key]
的属性,除了FieldType
。另外,不要测试ContainsKey
,然后测试GetValue
。它执行两次查找。使用TryGetValue
一次完成。您删除了一些不相关的内容,但这远远不是正确的MCVE。您未能创建最简单的示例,仍然有很多不足之处,但最重要的是,您未能创建完整且可验证的示例。我不能不做很多猜测就把你的代码按原样编译。说ClassManager
是一个简单的类列表并不能减少它。如何填充它,如何初始化主机,如何从其名称中查找插件。这就是你真正的问题可能隐藏的所有相关信息。实际上,TRTTIContext是记录,而不是类,所以我认为它不需要显式创建。当我介绍软件包时,问题就出现了,我不能简单地避免它们,我有很多用插件范例编写的代码。另外,在设置TypeCast
程序中的s、I、b之前,我最初调用了vCtx.Free
。我可以假设释放对象实际上并不会擦除它,但让我挑明一点:记录不是显式创建的,它是记录,所以它的内存不必分配给Create
,但这并不意味着可以省略其他初始化。同样的方法,Free
可以使记录中的数据无效,即使记录仍然存在于内存中。在这种情况下,我更喜欢遵循文档。
program test;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
USprPlugin in 'USprPlugin.pas',
UPlugin in 'UPlugin.pas';
var
Plugin: TPlugin;
begin
Plugin := TSprPlugin.Create;
Plugin.Init;
Plugin.SetProp('SprTableName', 'MATERIALS');
Plugin.SetProp('EditRights', True);
Plugin.SetProp('BeforePostValue1', 3);
end.
unit USprPlugin;
interface
uses
UPlugin, RTTI, UniversalSprUnit, Generics.Collections;
type
TSprPlugin = class(TPlugin)
vCtx: TRTTIContext;
procedure Init; override;
function SetProp(Key: string; Value: TValue): Boolean; override;
private
Spr: TSpr;
PropDict: TDictionary<string, TRTTIField>;
end;
implementation
procedure TSprPlugin.Init;
var
vType: TRTTIType;
vField: TRTTIField;
begin
vCtx := TRttiContext.Create;
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.