给定的代码在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.
答案 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}}字段在必要时减少其引用计数。