Как использовать Delphi RTTI для получения и установки значений записи - PullRequest
10 голосов
/ 18 января 2011

Я пытаюсь использовать расширенные функции 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;

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

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

Ответы [ 2 ]

11 голосов
/ 18 января 2011

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

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

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

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

4 голосов
/ 26 февраля 2012

Атрибуция: Первоначально опубликовано ОП как обновления вопроса ( Митч ).трюк.Вот пересмотренный код:

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