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 ...

  1. The PluginInterface package looks like this. There is a 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. 

    ... 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. 
  2. Create a VCL Forms Application , enable support for runtime packages, add a PluginInterface 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 inherit IPluginHost functionality. UClassManager and UPlugin must be specified in the uses . Other necessary shell units will add itself.

  3. Make the UniversalSpr package and configure it so that the compilation output file falls into the same directory as the host application. Make the TSprPlugin plugin, inheriting it from TPlugin :

     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.

    1 answer 1

    Maybe what I will write is not quite the answer, but the result of my observations, but I think it may come in handy ...

    Recently, I wrote a script for "Ida Pro," which could analyze rtti in executable files. When I entered the development, I looked at the sources of the RTTI module and analyzed in parallel how the data is located in the binary.

    And this is what I was able to draw from my analysis:

    1) The most important thing !!! Most of the internal rtti formats are described in typinfo.pas, but this does not provide complete information. That is, the structure of the type can have pitfalls that can be found only by analyzing the sources of rtti.pas and how this information is stored in the binary.

    2) Maybe rtti in .exe and in .bpl may be different. I found this out when I tried to run my .bpl script. In .exe, the script worked fine, and in .bpl, the entire analysis got stuck (if my memory serves me, the analysis would break down into some kind of structure).

    3) If you work with rtti in .bpl, then there are no guarantees that you can get all the information about the type or that everything will work without exception. I made this conclusion after writing a small code (the code used rtti.pas), which analyzed rtti in delphi itself (the code was connected as a plugin to delphi). During the operation of this code, there were occasionally very rare exceptions on certain types in the depths of the rtti.pas module.

    The conclusion from this is this: The internal structure of rtti is complex and the developers could simply be mistaken in rtti.pas. Your problem can and can be solved, but you have to dig around well to find out what the error is, and then you have to correct the error somehow.

    PS: Since the info on rtti is not documented in its entirety, I may well be mistaken about points 2.

    • In fact, judging by the documentation, all public fields should be published and then RTTI will work. I did a little differently, I refused RTTI altogether and in principle it turned out that it was even better. However, RTTI still came in handy and continues to come in handy. It allows you to use more elegant solutions in some places. - Danatela