Search code examples
delphigenericsdelphi-2010rtti

Delphi: How to set field value of a generic using RTTI?


I'd like to fill the field of a generic object at runtime using D2010.

program generic_rtti_1;
{$APPTYPE CONSOLE}
uses
  SysUtils, rtti;
type
  TMyObject = class
    FField1: string;
  end;
  TGeneric<TElement: class> = class
    procedure FillFields(Element: TElement);
  end;
procedure TGeneric<TElement>.FillFields(Element: TElement);
var
  ctx:  TRttiContext;
begin
  ctx := TRttiContext.Create();
  ctx.GetType(TypeInfo(TElement)).GetField('FField1').
    SetValue(@Element, TValue.FromVariant('Some string'));
  ctx.Free();
end;

When the line ctx.Free(); is executed, I get an AV at line 21986 in System.pas (function _IntfClear()). This is called from FContextToken := nil in rtti.pas. (In fact, the SetValue-induced AV pops up if I step into SetValue, however if step over it, only the ctx.Free-induced is reported. See below.)

If I remove ctx.Free();, the AV appears when calling SetValue(@Element, TValue.FromVariant('Some string'));. This too at line 21986 in System.pas.

Trying to figure this mess out, I replaced

ctx.GetType(TypeInfo(TElement)).GetField('FField1').
  SetValue(@Element, TValue.FromVariant('Field 1 is set'));

with this:

rType := ctx.GetType(TypeInfo(TElement));
rField := rType.GetField('FField1');
Val := TValue.FromVariant('Field 1 is set');
rField.SetValue(@Element, Val);

This time, I got no error, however WriteLn(MyObject.FField1) printed an empty string. (The AV re-appears if I combine SetValue and TValue.FromVariant, i.e. write rField.SetValue(@Element, TValue.FromVariant('Field 1 is set'));.

In order to pinpoint the guilty line, I commented out line by line, replacing the commented code with a compound statement. By accident I forgot to comment out the Val := TValue.FromVariant('Field 1 is set');-line above, which causes the AV to disappear once more (still calling rField.SetValue(@Element, TValue.FromVariant('Field 1 is set'));). (Note that I don't actually use Val in the troublesome call, still the AV disappears.)

I'm kind'a lost at this point.

For sake of completeness, here's how I'd like to use the above code:

var
  Generic:  TGeneric<TMyObject>;
  MyObject: TMyObject;
begin
  MyObject := TMyObject.Create();
  Generic := TGeneric<TMyObject>.Create();
  Generic.FillFields();
  WriteLn(MyObject.FField1);
  Generic.Free();
  MyObject.Free();
  ReadLn;
end;
end.

Do anyone know what I'm doing wrong? (Is this even possible? Are there better ways to do this using generics? )


Solution

  • Well, I don't know if this makes sense to you guys, but here's how I solved it. Hard cast to TObject in procedure TGeneric<TElement>.FillFields works like a charm. Like so:

    ctx.GetType(TypeInfo(TElement)).GetField('FField1').
      SetValue(TObject(Element), TValue.FromVariant('Field 1 is set'));
    

    Hope this is useful to someone else out there.