Delphi:如何使用 DynArraySetLength 设置 RTTI 访问的动态数组的长度?
我想设置动态数组的长度,如
TChildClass = class
private
FField1: string;
FField2: string;
end;
TMyClass = class
private
FField1: TChildClass;
FField2: Array of TChildClass;
end;
数组扩充的实现为 作为
var
RContext: TRttiContext;
RType: TRttiType;
Val: TValue; // Contains the TMyClass instance
RField: TRttiField; // A field in the TMyClass instance
RElementType: TRttiType; // The kind of elements in the dyn array
DynArr: TRttiDynamicArrayType;
Value: TValue; // Holding an instance as referenced by an array element
ArrPointer: Pointer;
ArrValue: TValue;
ArrLength: LongInt;
i: integer;
begin
RContext := TRTTIContext.Create;
try
RType := RContext.GetType(TMyClass.ClassInfo);
Val := RType.GetMethod('Create').Invoke(RType.AsInstance.MetaclassType, []);
RField := RType.GetField('FField2');
if (RField.FieldType is TRttiDynamicArrayType) then begin
DynArr := (RField.FieldType as TRttiDynamicArrayType);
RElementType := DynArr.ElementType;
// Set the new length of the array
ArrValue := RField.GetValue(Val.AsObject);
ArrLength := 3; // Three seems like a nice number
ArrPointer := ArrValue.GetReferenceToRawData;
DynArraySetLength(ArrPointer, ArrValue.TypeInfo, 1, @ArrLength);
{ TODO : Fix 'Index out of bounds' }
WriteLn(ArrValue.IsArray, ' ', ArrValue.GetArrayLength);
if RElementType.IsInstance then begin
for i := 0 to ArrLength - 1 do begin
Value := RElementType.GetMethod('Create').Invoke(RElementType.AsInstance.MetaclassType, []);
ArrValue.SetArrayElement(i, Value);
// This is just a test, so let's clean up immediatly
Value.Free;
end;
end;
end;
ReadLn;
Val.AsObject.Free;
finally
RContext.Free;
end;
end.
D2010 RTTI 的新手,我怀疑错误可能取决于从类实例获取 ArrValue,但随后的 WriteLn
打印“ TRUE”,所以我排除了这一点。然而,令人失望的是,相同的 WriteLn
报告 ArrValue 的大小为 0,这由“索引越界”证实 - 我在尝试设置数组中的任何元素时遇到的异常(通过 ArrValue.SetArrayElement(i, Value);
)。有谁知道我在这里做错了什么? (或者也许有更好的方法来做到这一点?)TIA!
I'd like to set the length of a dynamic array, as suggested in this post. I have two classes TMyClass and the related TChildClass defined as
TChildClass = class
private
FField1: string;
FField2: string;
end;
TMyClass = class
private
FField1: TChildClass;
FField2: Array of TChildClass;
end;
The array augmentation is implemented as
var
RContext: TRttiContext;
RType: TRttiType;
Val: TValue; // Contains the TMyClass instance
RField: TRttiField; // A field in the TMyClass instance
RElementType: TRttiType; // The kind of elements in the dyn array
DynArr: TRttiDynamicArrayType;
Value: TValue; // Holding an instance as referenced by an array element
ArrPointer: Pointer;
ArrValue: TValue;
ArrLength: LongInt;
i: integer;
begin
RContext := TRTTIContext.Create;
try
RType := RContext.GetType(TMyClass.ClassInfo);
Val := RType.GetMethod('Create').Invoke(RType.AsInstance.MetaclassType, []);
RField := RType.GetField('FField2');
if (RField.FieldType is TRttiDynamicArrayType) then begin
DynArr := (RField.FieldType as TRttiDynamicArrayType);
RElementType := DynArr.ElementType;
// Set the new length of the array
ArrValue := RField.GetValue(Val.AsObject);
ArrLength := 3; // Three seems like a nice number
ArrPointer := ArrValue.GetReferenceToRawData;
DynArraySetLength(ArrPointer, ArrValue.TypeInfo, 1, @ArrLength);
{ TODO : Fix 'Index out of bounds' }
WriteLn(ArrValue.IsArray, ' ', ArrValue.GetArrayLength);
if RElementType.IsInstance then begin
for i := 0 to ArrLength - 1 do begin
Value := RElementType.GetMethod('Create').Invoke(RElementType.AsInstance.MetaclassType, []);
ArrValue.SetArrayElement(i, Value);
// This is just a test, so let's clean up immediatly
Value.Free;
end;
end;
end;
ReadLn;
Val.AsObject.Free;
finally
RContext.Free;
end;
end.
Being new to D2010 RTTI, I suspected the error could depend on getting ArrValue from the class instance, but the subsequent WriteLn
prints "TRUE", so I've ruled that out. Disappointingly, however, the same WriteLn
reports that the size of ArrValue is 0, which is confirmed by the "Index out of bounds"-exception I get when trying to set any of the elements in the array (through ArrValue.SetArrayElement(i, Value);
). Do anyone know what I'm doing wrong here? (Or perhaps there is a better way to do this?) TIA!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
动态数组使用起来有点棘手。它们是引用计数的,DynArraySetLength 中的以下注释应该可以阐明这个问题:
// 如果堆对象不是共享的(引用计数 = 1),只需调整它的大小。否则,我们会创建一份副本。
您的对象持有对其的一个引用,TValue 也是如此。此外,GetReferenceToRawData 还为您提供了指向数组的指针。您需要说
PPointer(GetReferenceToRawData)^
才能获取要传递给 DynArraySetLength 的实际数组。一旦你得到了它,你可以调整它的大小,但你留下了一个副本。然后你必须将其设置回原始数组。
总而言之,仅使用列表而不是数组可能要简单得多。使用 D2010,您可以使用 Generics.Collections,这意味着您可以创建
TList
或TObjectList
并获得列表类的所有优点,而无需失去类型安全性。Dynamic arrays are kind of tricky to work with. They're reference counted, and the following comment inside DynArraySetLength should shed some light on the problem:
// If the heap object isn't shared (ref count = 1), just resize it. Otherwise, we make a copy
Your object is holding one reference to it, and so is the TValue. Also, GetReferenceToRawData gives you a pointer to the array. You need to say
PPointer(GetReferenceToRawData)^
to get the actual array to pass to DynArraySetLength.Once you've got that, you can resize it, but you're left with a copy. Then you have to set it back onto the original array.
All in all, it's probably a lot simpler to just use a list instead of an array. With D2010 you've got Generics.Collections available, which means you can make a
TList<TChildClass>
orTObjectList<TChildClass>
and have all the benefits of a list class without losing type safety.我认为你应该将数组定义为单独的类型:
并使用它。
从旧的基于 RTTI 的 XML 序列化器中,我知道您使用的一般方法应该有效(D7..2009 测试):
希望这有帮助..
I think you should define the array as a separate type:
and use that.
From an old RTTI based XML serializer I know the general method that you use should work (D7..2009 tested):
Hope this helps..