Reputation: 125
I have a number of record structures in Delphi (Berlin) which I am trying to recursively iterate through using RTTI. The code is not working for inner records. What am I doing wrong here?
Procedure WriteFields(Const RType : TRttiType;
Const Test : TTestRecord;
Var Offset : integer);
var
RFields : TArray<TRTTIField>;
i : integer;
Val : TValue;
begin
RFields := GetFields(Rtype);
try
for i := Low(RFields) to High(RFields) do
begin
if RFields[i].FieldType.TypeKind <> tkRecord then
begin
Val := rfields[i].GetValue(@Test);
writeln(Format('Field Name: %s, Type: %s, Value: %s, Offset: %d',[
RFields[i].Name,
RFields[i].FieldType.ToString,
Val.ToString,
RFields[i].Offset]));
end
else
begin
WriteLn(Format('------- Inner record : %s',[RFields[i].name]));
//recursively call this routine for the other records, and fields
Writefields(RFields[i].FieldType,Test,Offset);
end;
Offset := OffSet + RFields[i].Offset;
end;
finally
SetLength(RFIelds,0);
end;
end;
Here is my test record structure
TInfo = packed record
Age : integer;
end;
TTestRecord = packed record
Name : String;
Text : String;
Info : TInfo; //inner record structure
end;
Here's my test record data
//set a few values on it
Test.Name := 'Fred';
Test.text := 'Some random text';
Test.Info.Age := 50;
Here's the output of the code running in a console app
Size of 12
Field Name: Name, Type: string, Value: Fred, Offset: 0
Field Name: Text, Type: string, Value: Some text, Offset: 4
------- Inner record : Info
Field Name: Age, Type: Integer, Value: 38642604, Offset: 0
Total offset of bytes read 12
As you can see, the value returned for the inner record Age, is garbage.
Upvotes: 3
Views: 880
Reputation: 597961
You are not passing the inner record instance to WriteFields()
during the recursive call. You are passing the outer record instance again. Thus, the call to TRttiField.GetValue()
fails with undefined behavior, since you are giving it the wrong pointer.
If you change the 2nd input parameter to be a Pointer
(which is what TRttiField.GetValue()
expects anyway) or an untyped const
, then apply RFields[i].Offset
to that value when making recursive calls, your code will then work as expected.
For example:
Procedure WriteFields(const RType : TRttiType;
const Instance : Pointer);
var
RField : TRTTIField;
Val : TValue;
begin
for RField in RType.GetFields do
begin
if RField.FieldType.TypeKind <> tkRecord then
begin
Val := RField.GetValue(Instance);
WriteLn(Format('Field Name: %s, Type: %s, Value: %s, Offset: %d',[
RField.Name,
RField.FieldType.ToString,
Val.ToString,
RField.Offset]));
end
else
begin
WriteLn(Format('------- Inner record : %s, Offset: %d',[RField.Name, RField.Offset]));
//recursively call this routine for the other records, and fields
WriteFields(RField.FieldType, PByte(Instance)+RField.Offset);
WriteLn('-------');
end;
end;
end;
...
var
Test: TTestRecord;
...
WriteFields(..., @Test);
Or:
Procedure WriteFields(const RType : TRttiType;
const Instance);
var
RField : TRTTIField;
Val : TValue;
begin
for RField in RType.GetFields do
begin
if RField.FieldType.TypeKind <> tkRecord then
begin
Val := RField.GetValue(@Instance);
WriteLn(Format('Field Name: %s, Type: %s, Value: %s, Offset: %d',[
RField.Name,
RField.FieldType.ToString,
Val.ToString,
RField.Offset]));
end
else
begin
WriteLn(Format('------- Inner record : %s, Offset: %d',[RField.Name, RField.Offset]));
//recursively call this routine for the other records, and fields
WriteFields(RField.FieldType, (PByte(@Instance)+RField.Offset)^);
WriteLn('-------');
end;
end;
end;
...
var
Test: TTestRecord;
...
WriteFields(..., Test);
In both cases, the output is what you are expecting:
Field Name: Name, Type: string, Value: Fred, Offset: 0
Field Name: Text, Type: string, Value: Some random text, Offset: 4
------- Inner record : Info, Offset: 8
Field Name: Age, Type: Integer, Value: 50, Offset: 0
-------
Upvotes: 9