How to set record values ​​using RTTI in Delphi XE

I am trying to set record values ​​using RTTI in Delphi XE. I can get the value from the record using the GetValue method, but I can not set the value using the SetValue method.

Does anyone know how to do this / why it doesn't work?

Thanks in advance!

My context: The ultimate goal is to write a component that will read any XML file and automatically populate the application data model with XML data. Dunmatode will be annotated to determine XPaths for all elements. For objects and basic data types, I already have it and it works.

  TSize = record
    X, Y: double;
  end;

  TMyTest = class
  protected
    FSize: TSize;
  public
    constructor Create;
    procedure DoStuff;
  end;

constructor TMyTest.Create;
begin
  FSize.X := 2.7;
  FSize.Y := 3.1;
end;

procedure TMyTest.DoStuff;
var
  MyContext: TRttiContext;
  MyField: TRttiField;
  MySizeField: TRttiField;
  MyVal: TValue;
  MyRecord: TRttiRecordType;
  NewVal: TValue;
begin 
  // Explicit Create of MyContext does not help (as expected)
  for MyField in MyContext.GetType(ClassType).GetFields do
    if MyField.Name = 'FSize' then //For debugging
    begin
      MyRecord := MyField.FieldType.AsRecord;
      MyVal := MyField.GetValue(Self);
      for MySizeField in MyRecord.GetFields do
      begin
        //This works
        NewVal := MySizeField.GetValue(MyVal.GetReferenceToRawData).AsExtended;
        NewVal := NewVal.AsExtended + 5.0;
        try
          // This does not work. (no feedback)
          MySizeField.SetValue(MyVal.GetReferenceToRawData, NewVal);
          // This however does work. Now to find out what the difference between the two is.
          MySizeField.SetValue(@FSize, NewVal);
        except
          on e: Exception do //Never happens
            ShowMessage('Oops!' + sLineBreak + e.Message);
        end;
      end;
    end;
  // Shows 'X=2.7 Y=3.1'
  // Expected 'X=7.7 Y=8.1'
  ShowMessage(Format('X=%f Y=%f', [FSize.X, FSize.Y]));
end;

TSize , MyVal.GetReferenceToRawData TObject(MyVal.GetReferenceToRawData^). , , . (, MyVal.AsObject ) : Typecast MyVal.GetReferenceToRawData^ . ?

@FSize SetValue. , . : @FSize MyVal.GetReferenceToRawData​​p >


, MyVal , , , . , ...

, . , " ", , . , Follow.

procedure TMyTest.DoStuff;
var
  MyContext: TRttiContext;
  MyField: TRttiField;
  MySizeField: TRttiField;
  NewVal: TValue;
  dMyVal: double;
begin
  for MyField in MyContext.GetType(ClassType).GetFields do
    if MyField.Name = 'FSize' then
    begin
      for MySizeField in MyField.FieldType.GetFields do
      begin
        dMyVal := MySizeField.GetValue(PByte(Self) + MyField.Offset).AsExtended;
        NewVal := TValue.From(dMyVal + 5.1);
        try
          MySizeField.SetValue(PByte(Self) + MyField.Offset, NewVal);
        except
          on e: Exception do
            ShowMessage('Oops!' + sLineBreak + e.Message);
        end;
      end;
    end;
  if FSize.X > 5.0 then
    ShowMessage(Format('X=%f Y=%f', [FSize.X, FSize.Y]));
end;
+3
3

MyVal.GetReferenceToRawData FSize, FSize .

, - , , , :

uses rtti, typinfo;

type
  TSize = record
    X, Y: double;
  end;

  TMyTest = class
  protected
    FSize: TSize;
  public
    constructor Create;
    procedure DoStuff;
  end;

constructor TMyTest.Create;
begin
  FSize.X := 2.7;
  FSize.Y := 3.1;
end;

procedure TMyTest.DoStuff;
var
  MyContext: TRttiContext;
  MyField: TRttiField;
  MyVal: TValue;
  NewSize: TSize;

begin
  // Explicit Create of MyContext does not help (as expected)
  for MyField in MyContext.GetType(ClassType).GetFields do
    if MyField.Name = 'FSize' then //For debugging
    begin
      MyVal := MyField.GetValue(Self);
      NewSize:= MyVal.AsType<TSize>;
      NewSize.X:= NewSize.X + 5;
      NewSize.Y:= NewSize.Y + 5;
      TValue.Make(@NewSize, TypeInfo(TSize), MyVal);
      MyField.SetValue(Self, MyVal);
    end;
  ShowMessage(Format('X=%f Y=%f', [FSize.X, FSize.Y]));
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Test: TMyTest;

begin
  Test:= TMyTest.Create;
  Test.DoStuff;
  Test.Free;
end;
+1

, , , , TRttiProperty Offset , , .

procedure TMyTest.DoStuff;
var
  MyContext: TRttiContext;
  MyProp: TRttiProperty;
  MySizeField: TRttiField;
  NewVal: TValue;
  dMyVal: double;
  MyPointer:Pointer;
begin
  for MyProp in MyContext.GetType(ClassType).GetProperties do
    if MyProp.Name = 'Size' then
    begin
      MyPointer := TRttiInstanceProperty(MyProp).PropInfo^.GetProc;
      for MySizeField in MyProp.PropertyType.GetFields do
      begin
        dMyVal := MySizeField.GetValue(PByte(Self) + Smallint(MyPointer)).AsExtended;
        NewVal := TValue.From(dMyVal + 5.1);
        try
          MySizeField.SetValue(PByte(Self) + Smallint(MyPointer), NewVal);
        except
          on e: Exception do
            ShowMessage('Oops!' + sLineBreak + e.Message);
        end;
      end;
    end;
  if FSize.X > 5.0 then
    ShowMessage(Format('X=%f Y=%f', [FSize.X, FSize.Y]));
end;
0

RTTI works best with published properties. Therefore, if you add

published property size: TSize read fSize write fSize;

for your class I think you will have better results.

-2
source

All Articles