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

を呼び出すときは、ノードのデータの最初の4バイトAddChild(..., @TestArray[0])のみを初期化します。それがフィールドです。このフィールドは、構造体へのポインターを保持します。参照を保持することになっています。TextTextTTeststring

このGetNodeData関数は、ノードのデータへのポインタを返します。ツリーコントロールはTVirtualNodeレコードを割り当て、その直後に、連続するメモリに、NodeDataSize使用するバイトを割り当てGetNodeData、そのスペースのアドレスを返します。あなたはそれをTTest構造体へのポインタとして扱うことになっています。そして、あなたはあなたのコードのいくつかのためにそうします。を呼び出すと、構造体の最初の4バイトのみが初期化されるという制限を回避しようとしているようですAddChild。(私はそれをお勧めするとは言えません。型のパンニングをそれほど必要としないノードにデータを関連付ける方法は他にもあります。)

Dataノードデータの使用方法を正しく割り当てます。初期化時に実際に保持されているもの、つまり構造体へのポインターへのポインターをNodeData正しく割り当てます。フィールドを読み取るために正しく逆参照し、フィールドも正しく読み取ります。ただし、フィールドを現在の方法で上書きすることはできません。TTestNodeDataNumberTextData.Text

Data.Text := PTest(NodeData^)^.Text;

このData.Textフィールドは現在有効なstring値を保持していませんが、string変数は常に有効な値を保持する必要があります(または、少なくとも読み取りまたは書き込みが行われる可能性がある場合は常に)。変数を割り当てるためにstring、プログラムは新しい値の参照カウントをインクリメントし、古い値の参照カウントをデクリメントしますが、この場合の「古い値」は実際にはではないためstring、デクリメントする有効な参照カウントはありません。あったとしても、その場所のメモリはとにかく解放できませんでした—それはに属していTestArrayます。

ただし、これを回避する方法があります。文字列を2つの手順でコピーします。まず、から値をNodeData.Textスペアstring変数に読み込みます。これを行うと、もう必要がなくなるためNodeData、ポイントする値を上書きできます。Data.Textall-bits-zeroに設定すると、空の文字列の値で暗黙的に上書きされます。その時点で、変数として上書きしても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 に答える
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 に答える