Нарушение доступа при назначении строки в событии 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;
Другие вопросы по тегам