Как использовать Delphi RTTI для получения и установки значений записи

Я пытаюсь использовать расширенные функции RTTI в Delphi XE или более поздних версиях для чтения и записи объектов в XML. До сих пор я был успешным с целыми числами, плаваниями, строками, перечисляемыми типами, наборами и классами, но не мог правильно выводить или читать записи. Кажется, что проблема заключается в получении экземпляра (указателя) для свойства записи.

//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;

Я подозреваю, что если я смогу понять, как получить значения, то их установка не должна быть проблемой. Затем на массивы, о мальчик!

Обновления: См. ниже . (Миграция как отдельный ответ для улучшения видимости).

Ответ 1

Предполагаю, что вы пытаетесь сохранить значение поля типа записи типа Self, да?

Сначала вы должны получить значение поля, FProp.GetValue(Self). Скажем, вы положили это в переменную под названием FieldValue типа TValue. Затем вы можете сохранить поля значения записи по своему усмотрению, хотя вам, вероятно, захочется написать рекурсивную процедуру для него, так как поля записи могут сами быть полями. Полевой приемник для записей ожидает адрес записи (указатель на ее начало) для симметрии с установщиком; сеттер ожидает адрес, а не значение, потому что в противном случае не было бы простого способа изменить поле "in situ" в другом классе или записи, поскольку записи иначе передаются по значению.

Вы можете получить это с помощью FieldValue.GetReferenceToRawData, который вернет указатель на начало записей, хранящихся внутри TValue.

Надеюсь, это даст вам достаточно подсказок, чтобы продолжить.

Ответ 2

Атрибуция: Первоначально опубликовано как вопрос обновления OP (Mitch ) - Миграция как отдельный ответ для улучшения видимости.

Решение Барри сделало трюк. Здесь пересмотренный код:

    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 ;

Для тех, кому необходимо иметь дело с массивами:

    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 ;