delphi 我应该如何取消引用一个局部变量来获取TList中记录元素的地址?

cotxawn7  于 2022-11-04  发布在  其他
关注(0)|答案(2)|浏览(145)

我正在将一个 Delphi 应用程序从使用TTreeView转换为使用TVirtualStringTree;节点数据保存在TList中的TITemData记录中。

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

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

我想让显示的树以尽可能简单的方式启动和运行,然后随着我了解如何使用VirtualStringTree,逐步转换应用程序。()方法,它遍历TList元素并在VirtualStringTree中创建子节点。我[试图]在每次对VST的调用中传递一个指向每个TITemData记录的指针。(),然后会在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;

取消指针引用:

procedure TMyForm.vstGetData(...);
var
  idB: TItemData;
begin
  idB := node.getData^;
  CellText := idB.idName;
end;

很快就很明显,无论我尝试了多少种不同的编码方式,例如@itemData[i],我的代码编译每个vst节点的唯一次数实际上是获取idA局部变量的地址,并且每个树节点都指向idA所指向的最新TITemData记录。()一旦buildTreeFromItemData()完成,并且本地idA变量超出范围,即vst中每个节点的数据指针现在都无效。
我大多数尝试以某种方式遵从idA,并获取存储在idA中的TITemData的地址位置,都从 Delphi 语法检查器生成了“无效类型转换”,更不用说编译器了。
有一次,我尝试了这样的方法:

ptr1^ := @idA;

我不知道这对 Delphi 编译器实际上意味着什么。我知道我希望它意味着什么:我希望它的意思是“将ptr 1设置为存储在idA局部变量地址处的[dereferected]地址”。令我惊讶的是,它编译了,但调试器一命中该语句就崩溃了。(编译器认为“ptr 1 ^:=“是什么意思?)
最后,我想到了将idA类型转换为TObject的想法;至少到那时,我的想法是,编译器会知道我们至少处于解引用的领域,并且最终可能会让我找到真正需要传递给vst.addChild()的指针。
经过大量的实验,以及许多“无效的类型转换“,令人难以置信的是[至少对我来说]下面的代码工作!.....

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;

ptr 2现在从语法和语义上都已经从idA中删除了,编译器 finally 允许在PItemData(ptr 1 ^)中取消引用,尽管它只在我添加了PItemData(...)类型转换之后才允许。
我甚至不需要在vstGetText中取消引用这个指针!...

procedure TMyForm.vstGetText(...);
var
  idB: PItemData;
begin
  idB := PItemData(node.getData);
  CellText := idB.idName;
end;

树完美地显示出来,访问冲突也消失了。(注意:buildTreeFromItemData()中的实际代码要复杂得多,它会创建子节点的子节点,以创建一个多层的复杂树结构。)
虽然我经过了大量的尝试和错误,终于在今天凌晨1点找到了一个解决方案,但我很难相信我对局部变量的欺骗对于如此简单的事情来说真的是必要的。所以我的问题是:什么是正确的 Delphi 语法来获取我的TITemData记录的地址存储在一个普通的“idA:TITemData;”局部变量?

  • (我想这是我第一次向stackoverflow提问;我希望我已经把它表述得足够好了。我把代码保留到了说明这个问题所必需的最基本的部分,我无法完全重现我所经历的实验代码。不过,最后两个代码块中的解决方案是我的工作代码。如果我可以改进我表述问题和解释的方式,以满足stackoverflow的严格标准,请告诉我。)*
5sxhfpxr

5sxhfpxr1#

什么是正确的 Delphi 语法来获取我的TITemData记录的地址存储在一个普通的“idA:TITemData;”局部变量?
嗯......这个很简单。你这样做:@idA .
这里的问题是,这不是你想做的。你想在你的列表中有TItemData的地址(idA只是itemData中记录的副本)。
要获取TList<T>中的值的地址,不能使用TList<T>property Items[Index: Integer]: T,因为它只返回值的副本。需要使用property List: arrayofT,它将给予您直接访问存储值的底层数组,然后使用以下命令获取地址:@itemData.List[I] .
话虽如此,但我并不推荐这样做。没有人能保证指针会保持多长时间有效。列表上有很多操作可能会使该地址无效,或者使它指向错误的TItemData。如果在获得指针时itemData是不可变的,那么它应该是可以的。否则,最好像IVO GELOV在his answer中所描述的那样分配新的TItemData
对评论的回答:
是不是没有合法 Delphi 语法来实现我所做的?
你没有做到你认为你做的事情。

idA   := itemData[i]; //Copy the value from the list
myObj := TObject(@idA); //Take the address of iDA and pretend it's a TObject
ptr1  := pointer(myObj) //Now, pretend my TObject is a Pointer
ptr2  := PItemData(ptr1^); //Now, pretend ptr1 is pointing to a PItemData.
//I didn't test it, but I'm pretty sure you could have just written `ptr2 := PItemData((@iDA)^)`
vst.addChild(NIL, ptr2);

那么这段代码是做什么的呢?这里的“主事件”是ptr2 := PItemData(ptr1^)。它取idA的地址,取第一个 PointerSize(为简单起见,我假设向前为32位)字节,并假设它是PItemDataTItemData的前32位是什么?string,更准确地说是idName。也许考虑一下@idA = @idA.IdName会帮助你理解。
换句话说,vst.addChild(NIL, ptr2);vst.addChild(NIL, PChar(idA.idName));vst.addChild(NIL, @idA.idName[1]);相同(除了空字符串......但我离题了)。因此,您在vst中添加的不是itemData中记录的地址,而是字符串idA. idName的第一个字符的地址。
但是,您的困惑可能来自于认为记录是引用类型。它们不是,它们是值类型。

dwbf0jvd

dwbf0jvd2#

TVirtualTree的思想是把你的数据存储在节点中,TVirtualTree会为你的数据预留内存,这就是为什么你需要告诉树你的数据有多大。
因此,在创建表单时(在OnCreate处理程序内),您应该设置VirtualTree的NodeDataSize属性:

procedure TmyForm.TntFormCreate(Sender: TObject);
begin
  inherited;
  myVirtualTree.NodeDataSize:=SizeOf(TItemData);
end;

您还应该从VirtualTree处理OnFreeNode事件:

procedure TmyForm.myVirtualTreeFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
  Data:PItemData;
begin
  Data:=Sender.GetNodeData(Node);
  if Assigned(Data) then Finalize(Data^);
end;

如果要在树中创建新节点:

procedure TmyForm.CreateNewNode(data:TItemData);
var
  node: PItemData;
  P: PVirtualNode;
begin
  with myVirtualTree do
  begin
    P:=AddChild(Nil);
    node:=GetNodeData(P);
    node^:=data;
    // ReinitNode is very important - to update InternalNode precomputed
    // text width. Otherwise node can not be properly selected if
    // FullRowSelect is FALSE
    ReinitNode(P,False);
  end;
end;

您还需要处理来自TVirtualTree的其他事件,如OnGetText、onPaintText、onCompareNodes、onIncrementalSearch、onDblClick、onGetHint等。

相关问题