Нарушение доступа при назначении строки в событии InitNode объекта TVirtualStringTree - PullRequest
2 голосов
/ 06 июня 2011

Данный код работает без проблем в 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 ]

2 голосов
/ 06 июня 2011

Когда вы вызываете 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.Text инициализируйте Data.Number последним.Integer значения всегда можно перезаписать, независимо от их содержимого.

Что бы вы ни делали, убедитесь, что вы завершили запись в событии OnFreeNode:

var
  Data: PTest;
begin
  Data := Sender.GetNodeData;
  Finalize(Data^);
end;

Это гарантирует, что поле string при необходимости уменьшит количество ссылок.

2 голосов
/ 06 июня 2011

Вы упускаете суть здесь. Вы уже инициализировали свой узел по событию нажатия кнопки, поэтому нет необходимости использовать OnInitNode для его дополнительного запуска. Вероятно, вам нужно использовать OnGetText для отображения ваших данных. E.g.:

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;
...