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