使用 RTTI 递归迭代 delphi 中的内部记录
using RTTI to recursively iterate inner records in delphi
我在 Delphi(柏林)中有许多记录结构,我试图通过使用 RTTI 递归迭代。该代码不适用于内部记录。我在这里做错了什么?
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;
这是我的测试记录结构
TInfo = packed record
Age : integer;
end;
TTestRecord = packed record
Name : String;
Text : String;
Info : TInfo; //inner record structure
end;
这是我的测试记录数据
//set a few values on it
Test.Name := 'Fred';
Test.text := 'Some random text';
Test.Info.Age := 50;
这是代码 运行 在控制台应用程序中的输出
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
如您所见,为内部记录 Age 返回的值是垃圾。
您在递归调用期间没有将内部记录实例传递给 WriteFields()
。您正在再次传递外部记录实例。因此,对 TRttiField.GetValue()
的调用因未定义的行为而失败,因为您给了它错误的指针。
如果您将第二个输入参数更改为 Pointer
(这是 TRttiField.GetValue()
所期望的)或未类型化的 const
,则将 RFields[i].Offset
应用于该参数进行递归调用时的值,您的代码将按预期工作。
例如:
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);
或:
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);
在这两种情况下,输出都是您所期望的:
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
-------
我在 Delphi(柏林)中有许多记录结构,我试图通过使用 RTTI 递归迭代。该代码不适用于内部记录。我在这里做错了什么?
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;
这是我的测试记录结构
TInfo = packed record
Age : integer;
end;
TTestRecord = packed record
Name : String;
Text : String;
Info : TInfo; //inner record structure
end;
这是我的测试记录数据
//set a few values on it
Test.Name := 'Fred';
Test.text := 'Some random text';
Test.Info.Age := 50;
这是代码 运行 在控制台应用程序中的输出
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
如您所见,为内部记录 Age 返回的值是垃圾。
您在递归调用期间没有将内部记录实例传递给 WriteFields()
。您正在再次传递外部记录实例。因此,对 TRttiField.GetValue()
的调用因未定义的行为而失败,因为您给了它错误的指针。
如果您将第二个输入参数更改为 Pointer
(这是 TRttiField.GetValue()
所期望的)或未类型化的 const
,则将 RFields[i].Offset
应用于该参数进行递归调用时的值,您的代码将按预期工作。
例如:
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);
或:
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);
在这两种情况下,输出都是您所期望的:
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
-------