How to use Delphi RTTI to get and set Record Values

Mitch picture Mitch · Jan 17, 2011 · Viewed 10k times · Source

I'm attempting to use the enhanced RTTI features in Delphi XE or later, to read and write objects to XML. So far I've been successful with integers, floats, strings, enumerated types, sets and classes but can't output or read records correctly. The problem seems to be getting an instance (pointer) to the record property.

//Outputs Properties To XML
procedure TMyBase.SaveToXML(node: TJclSimpleXMLElem);
var
  child , subchild : TjclSimpleXMLElem ;
  FContext : TRttiContext ;
  FType    : TRttiType ;
  FProp    : TRttiProperty ;
  Value    : TValue ;
  MyObj    : TMyBase ;
  FField   : TRttiField ;
  FRecord  : TRttiRecordType ;
  Data     : TValue ;
begin
  FContext := TRttiContext.Create ;
  FType := FContext.GetType ( self.ClassType ) ;
  Child := node.Items.Add ( ClassName ) ;
  for FProp in FType.GetProperties do begin
    if FProp.IsWritable then begin
      case FProp.PropertyType.TypeKind of
        tkClass : begin
          MyObj := TMyBase ( FProp.GetValue ( self ).AsObject ) ;
          MyObj.SaveClass ( Child.Items.Add ( FProp.Name ) , FContext ) ;
          end ;
        tkRecord : begin
          subchild := Child.Items.Add ( FProp.Name ) ;
          FRecord := FContext.GetType(FProp.GetValue(self).TypeInfo).AsRecord ;
          for FField in FRecord.GetFields do begin
            >>> self is not the correct instance <<<
            Value := FField.GetValue ( self ) ;
            subchild.Items.Add ( FField.Name ).Value := Value.ToString ;
            end;
          end ;
        else begin
          Value := FProp.GetValue(self) ;
          Child.Items.Add ( FProp.Name ).Value := Value.ToString ;
          end;
        end;
      end ;
    end ;
  FContext.Free ;
end;

I suspect that if I can figure out how to get the values then setting them shouldn't be a problem. Then onto arrays, oh boy!

Updates: Please see below. (Migrated as separate answer to improve visibility).

Answer

Barry Kelly picture Barry Kelly · Jan 18, 2011

I presume you are trying to save the value of a record-typed field of the runtime type of Self, yes?

You have to get the value of the field first, with FProp.GetValue(Self). Let's say you put that in a variable called FieldValue of type TValue. You can then save the fields of the record value as you wish, though you'll probably want to write a recursive procedure for it, as the fields of the record may themselves be fields. The field getter for records expects the address of the record (a pointer to its start) for symmetry with the setter; the setter expects the address rather than the value because otherwise there would be no easy way to modify a field "in situ" in another class or record, since records are otherwise passed around by value.

You could get that with FieldValue.GetReferenceToRawData, which will return a pointer to the start of the records stored inside the TValue.

Hopefully this gives you enough clues to continue.