对象的 VirtualTreeView 内存泄漏

VirtualTreeView Memory Leak with Objects

我正在使用 Cosmin Prund in this post 提供的代码,因为它符合我的需要,但是我经常遇到内存泄漏,我不知道如何释放 TNode 包含的对象的节点TObjectList 反过来这最后一个也可以包含 TNode 也包含 TObjectList 等等......不过我是某种递归,

据我所知,要根据此 link 释放 VirtualTreeView 中的节点,需要验证节点并在 OnFreeNode 事件中完成此代码 return 无效的指针操作,当然还有内存泄漏报告:

procedure TfrmFichePermission.VSTFreeNode(Sender: TBaseVirtualTree;
  Node: PVirtualNode);
var
  AObject:TObject;
  ANode: TNode;
begin
  AObject := TObject(VST.GetNodeData(Node)^);
  ANode := TNode(AObject);
  ANode.Free;
end;

这是重现内存泄漏的完整示例

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, VirtualTrees, System.Generics.Collections,
  Vcl.StdCtrls;

type
  TNode = class;

  TForm1 = class(TForm)
    VST: TVirtualStringTree;
    Button1: TButton;
    procedure VSTGetNodeDataSize(Sender: TBaseVirtualTree;
      var NodeDataSize: Integer);
    procedure VSTGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
      Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
    procedure Button1Click(Sender: TObject);
  private
    procedure AddNodestoTree(ParentNode: PVirtualNode; Node: TNode);
  public
    { Public declarations }
  end;

  TNode = class
  public
    Name: string;
    VTNode: PVirtualNode;
    Sub: TObjectList<TNode>;
    constructor Create(aName: string);
    destructor Destroy; override;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

constructor TNode.Create(aName:string);
begin
  Name := aName;
  Sub := TObjectList<TNode>.Create;
end;

destructor TNode.Destroy;
begin
  Sub.Free;
end;

procedure TForm1.AddNodestoTree(ParentNode: PVirtualNode; Node: TNode);
var SubNode: TNode;
    ThisNode: PVirtualNode;
begin
  ThisNode := VST.AddChild(ParentNode, Node); // This call adds a new TVirtualNode to the VT, and saves "Node" as the payload

  Node.VTNode := ThisNode; // Save the PVirtualNode for future reference. This is only an example,
                           // the same TNode might be registered multiple times in the same VT,
                           // so it would be associated with multiple PVirtualNode's.

  for SubNode in Node.Sub do
    AddNodestoTree(ThisNode, SubNode);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Root: TNode;
begin
  ReportMemoryLeaksOnShutdown := True;
  VST.Clear;
  //
  Root := TNode.Create('Test1');
  Root.Sub.Add(TNode.Create('Test2'));
  Root.Sub.Add(TNode.Create('Test3'));
  Root.Sub[1].Sub.Add(TNode.Create('Test4'));
  Root.Sub[1].Sub.Add(TNode.Create('Test5'));
  AddNodesToTree(nil, Root);
  //
  Root := TNode.Create('Test1');
  Root.Sub.Add(TNode.Create('Test2'));
  Root.Sub.Add(TNode.Create('Test3'));
  Root.Sub[1].Sub.Add(TNode.Create('Test4'));
  Root.Sub[1].Sub.Add(TNode.Create('Test5'));
  AddNodesToTree(nil, Root);
  //
end;
procedure TForm1.VSTGetNodeDataSize(Sender: TBaseVirtualTree;
  var NodeDataSize: Integer);
begin
  NodeDataSize := SizeOf(Pointer);
end;

procedure TForm1.VSTGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
    AObject:TObject;
    ANode: TNode;
begin
  AObject := TObject(VST.GetNodeData(Node)^);
  ANode := TNode(AObject);
  CellText := ANode.Name;
end;

end.

内存泄漏报告:

Cosmin 的代码并不打算让树视图节点拥有 TNode 对象。我认为在他的 post 中你应该抓住 Root 对象并在树被摧毁后将其摧毁。

在 Cosmin 的代码中,TNode 个对象属于包含它们的对象列表。一直到创建它的任何人所拥有的根节点。你也可以这样做。您必须记住根对象,并在树视图节点被销毁时停止销毁 TNode 对象。

如果您想让树视图拥有 TNode 个对象,那么您可以这样做。但是你需要清楚所有权。您不能像现在那样使用树视图 对象列表来拥有对象。如果树视图将成为所有者,则需要在对象列表中将 OwnsObjects 设置为 False。或者甚至更好地切换到 TList<TNode>

因此,总而言之,您当前的代码为每个 TNode 对象提供了两个所有者。树视图节点和所属对象列表。对象需要只有一个所有者。您需要在这两个选项之间做出选择。