Как использовать 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 ;