2

给定的代码在 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.
4

2 回答 2

2

你错过了这里的重点。您已经在按钮的单击事件上初始化了节点,因此无需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;
于 2011-06-06T08:22:55.523 回答
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变量,程序会增加新值的引用计数并减少旧值的引用计数,但由于这种情况下的“旧值”不是真正的 a string,因此没有有效的引用计数减量,并且即使有,那个位置的内存无论如何也无法释放——它属于TestArray.

不过,有办法解决这个问题。分两步复制字符串。首先,将值读取NodeData.Text到备用string变量中。一旦你这样做了,你就不再需要NodeData了,所以你可以覆盖它指向的值。如果将其设置为所有位为零,那么您也将隐式覆盖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字段的引用计数减少。

于 2011-06-06T14:39:19.380 回答