You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Delphi中如何获取局部变量内TItemData记录的地址?

Delphi中获取记录地址的正确方式及VirtualStringTree数据绑定问题

问题背景

我正在将Delphi应用从TTreeView迁移到TVirtualStringTree,节点数据存储在TList<TItemData>中,类型定义如下:

type
  TItemData = record
    idName: string;
  end;
  PItemData = ^TItemData;

  TMyForm = class(TForm)
  ...
  private
    itemData: TList<TItemData>;
  ..
  end;

为快速搭建树形显示,我编写了buildTreeFromItemData方法遍历TList创建VST子节点,尝试将每个TItemData的指针传给vst.AddChild,以便在vstGetText中解引用获取数据:

procedure buildTreeFromItemData;
var 
  i:  integer;
  idA: TItemData;
begin
  for i := 0 to itemData.Count - 1 do begin
    idA := itemData[i];
    vst.AddChild(NIL, @idA);
  end;
end;

对应的vstGetText代码:

procedure TMyForm.vstGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
  idB: TItemData;
begin
  idB := PItemData(Node.GetData)^;
  CellText := idB.idName;
end;

遇到的问题

所有VST节点实际获取的都是局部变量idA的地址,指向循环最后一次的TItemData记录。当buildTreeFromItemData执行完毕,idA超出作用域,vstGetText中会出现访问违规,所有节点的数据指针均失效。

我尝试过多种方式获取idA中存储的TItemData记录地址,但大多触发Delphi语法检查的“invalid typecast”错误。比如以下代码编译通过但运行时崩溃:

ptr1^ := @idA;

最后我通过复杂的类型转换实现了正常运行:

procedure buildTreeFromItemData;
var
  i:     integer;
  idA:   TItemData;
  myObj: TObject;
  ptr1:  pointer;
  ptr2:  PItemData;
begin
  for i := 0 to itemData.Count - 1 do begin
    idA   := itemData[i];
    myObj := TObject(@idA);
    ptr1  := Pointer(myObj);
    ptr2  := PItemData(ptr1^);
    vst.AddChild(NIL, ptr2);
  end;
end;

对应的vstGetText无需解引用:

procedure TMyForm.vstGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
  idB: PItemData;
begin
  idB := PItemData(Node.GetData);
  CellText := idB.idName;
end; 

但这种复杂转换并非必要,我想请教:获取普通局部变量idA: TItemData;中存储的TItemData记录地址的正确Delphi语法是什么?


正确解决方案

首先要明确:你需要的是TList中原始TItemData实例的地址,而非局部变量idA的地址——idA := itemData[i];是把TList中的记录复制到局部栈变量中,@idA取的是这个临时副本的地址,方法执行后栈内存会被回收,因此不能用于VST节点绑定。

方式一:直接获取TList中元素的原始地址

通过TList<T>.List属性可以访问底层数组指针,直接获取原元素的地址:

procedure buildTreeFromItemData;
var 
  i:  integer;
begin
  for i := 0 to itemData.Count - 1 do begin
    // 直接取TList中第i个元素的原始地址
    vst.AddChild(NIL, @itemData.List[i]);
  end;
end;

对应的vstGetText代码:

procedure TMyForm.vstGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
  idB: PItemData;
begin
  idB := PItemData(Node.GetData);
  CellText := idB.idName;
end;

方式二:为节点分配独立内存存储副本

如果后续需要修改节点数据且不想影响原TList内容,可以手动分配内存存储记录副本:

procedure buildTreeFromItemData;
var 
  i:  integer;
  pData: PItemData;
begin
  for i := 0 to itemData.Count - 1 do begin
    // 分配内存
    New(pData);
    // 复制TList中的记录到新内存
    pData^ := itemData[i];
    // 传递指针给VST
    vst.AddChild(NIL, pData);
  end;
end;

注意:这种方式需要在VST的OnFreeNode事件中释放内存,避免内存泄漏:

procedure TMyForm.vstFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
  pData: PItemData;
begin
  pData := PItemData(Node.GetData);
  Dispose(pData);
end;

关于你的核心疑问

局部变量idA本身就是TItemData类型,@idA就是它的地址,但这是栈上的临时地址,方法执行后会失效,不能用于VST节点绑定。你真正需要的是原数据的地址,而非局部副本的地址,因此上述两种方式才是正确选择。

你之前的复杂类型转换能工作只是侥幸——循环结束后栈内存尚未被覆盖,但这种写法本质不安全,后续极易出现访问违规。

内容的提问来源于stack exchange,提问作者Baz Cuda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 01:55:12