Нарушение доступа при назначении строки в событии InitNode объекта TVirtualStringTree
Данный код, который работает без каких-либо проблем в Delphi 2007. Однако в Delphi 2009 я получаю исключение.
Нарушение прав доступа показывает чтение адреса $ 00000000.
Проблема существует только при назначении строки, она работает для чисел.
Кроме того, когда я назначаю Data.Text
вручную через параметры отладчика я не получаю AV - все работает.
Честно говоря, я заблудился, кто-нибудь может мне помочь с этим, пожалуйста?
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, VirtualTrees, StdCtrls;
type
TTest = record
Text: String;
Number: Integer;
end;
PTest = ^TTest;
type
TTestArray = array of TTest;
type
TForm1 = class(TForm)
VirtualStringTree1: TVirtualStringTree;
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure VirtualStringTree1InitNode(Sender: TBaseVirtualTree; ParentNode,
Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
TestArray: array of TTest;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
SetLength(TestArray, 1);
TestArray[0].Text := 'test';
TestArray[0].Number := 12345;
VirtualStringTree1.AddChild(VirtualStringTree1.RootNode, @TestArray[0]);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
VirtualStringTree1.NodeDataSize := SizeOf(TTest);
end;
procedure TForm1.VirtualStringTree1InitNode(Sender: TBaseVirtualTree;
ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
var
Data: PTest;
NodeData: PPointer;
begin
Data := Sender.GetNodeData(Node);
NodeData := Sender.GetNodeData(Node);
Data.Number := PTest(NodeData^)^.Number;
Data.Text := PTest(NodeData^)^.Text; //crash here!
end;
end.
2 ответа
Когда вы звоните AddChild(..., @TestArray[0])
Вы только инициализируете первые четыре байта данных узла. Это Text
поле. Text
поле содержит указатель на TTest
состав. Это должно держать string
ссылка.
GetNodeData
Функция возвращает указатель на данные узла. Элемент управления дерева выделил TVirtualNode
запись, и сразу после этого, в последовательной памяти, он выделил NodeDataSize
байты для использования, и GetNodeData
возвращает адрес этого пространства. Вы должны рассматривать это как указатель на TTest
состав. И вы делаете, для некоторого вашего кода. Похоже, вы пытаетесь обойти ограничение, что только первые четыре байта структуры инициализируются при вызове AddChild
, (Я не могу сказать, что рекомендую это. Существуют другие способы связать данные с узлом, которые не требуют такого большого количества типов.)
Вы назначаете Data
правильно для способа, которым предполагается использовать данные узла. Вы назначаете NodeData
правильно для того, что он действительно держит в момент инициализации - указатель на указатель на TTest
состав. Вы правильно разыграли NodeData
читать Number
поле, и вы также прочитали Text
поле правильно. Тем не менее Data.Text
поле не может быть перезаписано так, как оно есть у вас:
Data.Text := PTest(NodeData^)^.Text;
Data.Text
поле не является действительным string
ценность, но string
переменные должны всегда содержать допустимые значения (или, по крайней мере, всегда, когда есть вероятность, что они будут прочитаны или записаны). Чтобы назначить string
переменной, программа увеличивает счетчик ссылок нового значения и уменьшает счетчик ссылок старого, но поскольку "старое значение" в этом случае на самом деле не string
нет действительного числа ссылок на декремент, и даже если бы он был, память в этом месте не могла быть освобождена в любом случае - она принадлежит TestArray
,
Хотя есть способ обойти это. Скопируйте строку в два этапа. Сначала прочитайте значение из NodeData.Text
в запасной string
переменная. Как только вы это сделаете, вам не нужно NodeData
больше, так что вы можете перезаписать значение, на которое он указывает. Если вы установите его в all-bits-zero, то вы неявно перезапишете Data.Text
а также со значением пустой строки. На этом этапе можно перезаписать как string
переменная:
tmp := PTest(NodeData^)^.Text;
PTest(NodeData^) := nil;
Data.Text := tmp;
Другим способом решения этой проблемы является изменение порядка полей в данных узла. Положить Integer
поле сначала, и инициализировать Data.Number
последний вместо Data.Text
, Integer
значения всегда безопасны для перезаписи, независимо от их содержимого.
Что бы вы ни делали, убедитесь, что вы завершили запись в OnFreeNode
событие:
var
Data: PTest;
begin
Data := Sender.GetNodeData;
Finalize(Data^);
end;
Это гарантирует, что string
Поле уменьшает количество ссылок, если это необходимо.
Вы упускаете суть здесь. Вы уже инициировали свой узел по событию нажатия кнопки, поэтому нет необходимости использовать OnInitNode
инициировать это дополнительно. Что вам нужно, это, вероятно, использовать OnGetText
для отображения ваших данных. Например:
procedure TForm1.VirtualStringTree1GetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
Data: PTest;
begin
Data := PTest(Sender.GetNodeData(Node)^);
CellText := Data.Text;
end;