There is an application consisting of the main module, plug-in plug-ins, implemented as bpl
and PluginInterface
link. In plug-ins there are a lot of structures, more precisely classes. It is required to name all the fields in these structures. To check that the problem is solved, I wrote a simple application:
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.
Output:
Hello World! 123 TRUE
It works in this form without problems. But when I start using this technique in already written packages, some kind of poltergeist begins, I'm not afraid of this word.
MCVE / To play:
... it is assumed that the environment is initially empty ...
The
PluginInterface
package looks like this. There is aUClassManager
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.
... and
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.
Create a
VCL Forms Application
, enable support for runtime packages, add aPluginInterface
to its dependencies and put a button on the form. On the relevant events, hang up these handlers: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;
Naturally,
TForm1
should inheritIPluginHost
functionality.UClassManager
andUPlugin
must be specified in theuses
. Other necessary shell units will add itself.Make the
UniversalSpr
package and configure it so that the compilation output file falls into the same directory as the host application. Make theTSprPlugin
plugin, inheriting it fromTPlugin
: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 // тут происходит нарушение доступа Field.SetValue(Spr, Value); end; end.
After pressing the button on the form, you can call the setter / mutator of the declared properties, but an attempt to repeat the tackcast trick inside our setter will result in Access Violation when accessing 00000004
.
A lengthy investigation and guerrilla interrogation of the debugger helped to find out that the Field.FieldType Field.FieldType
calculated correctly, and this explains why the InsufficientRTTI
Field.FieldType.Handle
is Field.FieldType.Handle
; The handle is called inside the SetValue
method, and can be removed in principle, which I did:
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;
However, then a type conversion will not be invoked, which is quite dangerous if someone else decides to take advantage of the fruits of my labors. In English, SO was recommended to me not to use packages, but this does not seem real. Is it possible to solve the error with getting the handle without refusing to use the packages ?
PS Please do not criticize for memory leaks, the original application has all the necessary methods for its release. I just gave the necessary code to reproduce the problem.