2

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

1 Answer 1

7

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Of course it makes sense since SetValue takes a pointer and an object is a pointer. I think you can even go without the hardcast as long as you got the class constraint on your generic type.
@StefanGlienke Well, I did pass in the address of Element in my first go. Meanwhile, the constraint was class all the time. This did not get picked up by the compiler, so I had to start experimenting. Now, the constraint class basically means TObject - right? If you try to constrain your generics to TObject, you'll be nicely told that 'TObject is not a valid constraint'. So this seems to be the minimal solution.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.