在TVirtualStringTree的InitNode事件中分配字符串时访问冲突

时间:2011-06-06 07:51:49

标签: string delphi delphi-2009 virtualtreeview 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 个答案:

答案 0 :(得分:2)

你错过了这一点。您已经在按钮的单击事件中进入了节点,因此无需使用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;

答案 1 :(得分:2)

当您致电AddChild(..., @TestArray[0])时,您只是初始化节点数据的前四个字节。这是Text字段。 Text字段包含指向TTest结构的指针。 假定来保存string引用。

GetNodeData函数返回指向节点数据的指针。树控件已分配一个TVirtualNode记录,紧接着在连续内存中,它已分配NodeDataSize个字节供您使用,GetNodeData返回该空间的地址。您应该将其视为指向TTest结构的指针。对于你的一些代码,你也这样做。看起来你正试图避免在调用AddChild时只有结构的前四个字节被初始化的限制。 (我不能说我推荐。还有其他方法可以将数据与不需要太多类型惩罚的节点相关联。)

您正确地为节点数据的使用方式分配Data。您可以正确地为{em>真正在初始化时保存的内容分配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

确保{{1}}字段在必要时减少其引用计数。