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).
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.
Attribution: Originally posted as question's updates by the OP (Mitch ) - Migrated as separate answer to improve visibility.
Barry's solution did the trick. Here's the revised code:
tkRecord : begin
subchild := Child.Items.Add ( FProp.Name ) ;
Value := FProp.GetValue(self) ;
FRecord := FContext.GetType(FProp.GetValue(self).TypeInfo).AsRecord ;
for FField in FRecord.GetFields do begin
Data := FField.GetValue ( Value.GetReferenceToRawData ) ;
subchild.Items.Add ( FField.Name ).Value := Data.ToString ;
end;
end ;
For those that need to deal with arrays:
tkDynArray : begin
Value := FProp.GetValue ( self ) ;
FArray := FContext.GetType(Value.TypeInfo) as TRttiDynamicArrayType ;
subchild := child.Items.Add ( FProp.Name ) ;
cnt := Value.GetArrayLength ;
subchild.Properties.Add ( 'Count' , cnt ) ;
case FArray.ElementType.TypeKind of
tkInteger ,
tkFloat : begin
for a := 0 to cnt-1 do begin
Data := Value.GetArrayElement ( a ) ;
subchild.Items.Add ( IntToStr(a) , Data.ToString ) ;
end;
end ;
tkRecord : begin
FRecord := FArray.ElementType as TRttiRecordType ;
for a := 0 to cnt-1 do begin
Data := Value.GetArrayElement ( a ) ;
subsubchild := subchild.Items.Add ( IntToStr(a) ) ;
for FField in FRecord.GetFields do
SaveField ( subsubchild , FContext , FField , Data.GetReferenceToRawData ) ;
end;
end ;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With