【问题标题】:How to properly free a Delphi form如何正确释放 Delphi 表单
【发布时间】:2018-03-14 08:52:11
【问题描述】:

尝试在完全调试模式下使用 FastMM 修复我的应用程序中的内存泄漏,报告了一些关于 TForm(后代)实例的泄漏。在检查并单步执行代码之后,我可以肯定地说这些表单是released 而不是freed。看起来 FastMM 在 Release 计划的实际释放发生之前正在检查泄漏。

MyForm: TForm;
MyForm := TForm.Create(nil);
...
MyForm.Release; // FastMM reports MyForm as a leak

我尝试释放它们,这些内存泄漏不再被报告,但有时,释放时发生访问冲突:

MyForm: TForm;
MyForm := TForm.Create(nil);
...
MyForm.Free; // // FastMM does not report MyForm as a leak, but sometimes an access violation is triggered

我尝试释放它们,然后立即调用 Application.ProcessMessages,据我所知,可以阅读 Zoë Peterson 在https://stackoverflow.com/a/917187/1465896 中的回答。我一定理解错了,因为它总是在 Application.ProcessMessages 中立即崩溃:

MyForm: TForm;
MyForm := TForm.Create(nil);
...
MyForm.Release; 
Application.ProcessMessages; // always triggers an access violation 

我不想将这些表单注册为预期的内存泄漏,因为它们包含更多的对象,这些对象会使内存泄漏日志文件变得混乱,从而更难找到更重要的泄漏。

所以我的问题是,如何正确释放 Delphi 表单,以便 FastMM 不会将其报告为泄漏?

根据要求,这是一个 SSCnCE(短、自包含、正确(不能按原样编译,因为我不知道如何发布项目),示例):

文件FormRelease.dpr:

program FormRelease;

uses
  FastMM4 in 'FastMM4.pas',
  Vcl.Forms,
  MainForm_fm in 'MainForm_fm.pas' {MainForm},
  MyForm_fm in 'MyForm_fm.pas' {MyForm};

{$R *.res}

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TMainForm, MainForm);
  Application.Run;
end.

文件 MainForm_fm.pas:

unit MainForm_fm;

interface

uses
  Vcl.Forms,
  MyForm_fm;

type
  TMainForm = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);

  private
    FMyForm: TMyForm;
  end;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}

procedure TMainForm.FormCreate(Sender: TObject);
begin
  FMyForm := TMyForm.Create(nil);
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  FMyForm.Release;
end;

end.

文件 MyForm_fm.pas:

unit MyForm_fm;

interface

uses
  Vcl.Forms;

type
  TMyForm = class(TForm)
  end;

implementation

{$R *.dfm}

end.

文件 FormRelease_MemoryManager_EventLog.txt 的摘录,在启动程序并使用 alt-F4 停止它之后:

This application has leaked memory. The small block leaks are (excluding expected leaks registered by pointer):

13 - 20 bytes: TList x 1, Unknown x 1
21 - 36 bytes: TPen x 1, TMargins x 1, TPadding x 1, TIconImage x 1, TBrush x 2, TTouchManager x 1, TSizeConstraints x 1, UnicodeString x 1, Unknown x 3
37 - 52 bytes: TGlassFrame x 1, TFont x 2
53 - 68 bytes: TIcon x 1
69 - 84 bytes: TControlScrollBar x 2
101 - 116 bytes: TControlCanvas x 1
149 - 164 bytes: Unknown x 1
917 - 1012 bytes: TMyForm x 1

如果用FMyForm.Release发布的CM_RELEASE已经被处理,为什么还剩下TMyForm x 1?

【问题讨论】:

  • 我认为您链接的答案不会促进您自己调用 Application.ProcessMessages 。它指出,当使用 Release 时,将在那里处理生成的消息。你能分解你用来创建和销毁表单的代码吗?这对任何回答者都很重要。
  • Release后直接调用Application.ProcessMessages应该和调用Free基本一样。没有延迟,发布是为了什么。您是否在表单本身的事件处理程序中这样做?这将解释访问冲突。
  • @Nil 我不是从事件处理程序中执行此操作,而是从常规析构函数,甚至从 finalization 部分执行此操作(我必须修复的应用程序具有一些特殊的架构)。跨度>
  • @TomBrunberg 我非常确定这是我正在处理的应用程序特有的问题,因此我没有考虑尝试创建示例。我错了!我添加了一个非常简单的示例,显示调用 Release 不会释放表单。
  • 此处发布错误。 ProcessMessages 在这里几乎总是错误的。调用 Free 以销毁对象。或者通过表单使其成为所有者。

标签: delphi fastmm


【解决方案1】:

这听起来像是两个不同的东西。

首先,您正在显式创建表单,因此您应该显式释放它。您不需要调用 Release,除非有特殊情况,例如从自己的事件处理程序中释放自己的表单。

MyForm: TForm;
...
MyForm := TForm.Create(nil);
try
  // Only reference MyForm in this block
finally
  MyForm.Free;
end;

其次,调用 Free 不会导致问题。我认为它揭示了它。如果我不得不猜测,访问冲突正在发生,因为 MyForm 或其组件之一在此块之外被引用。使用调试器找到这个引用并修复它。

【讨论】:

  • 同意:我明确地Release 表单,据我了解,与Free 完全相同,除了不是立即的,以允许事件处理程序在释放他们正在工作的内容之前完成他们的工作开。
  • 关于 Free 不会导致问题,是的,它可能只是暴露了它。但是,如果偶尔出现我可以修复的崩溃,我永远无法确定不会再发生一次崩溃,对吗?
  • 如果您自己控制表单的创建和释放,那么您需要确保它不会在该范围之外被访问。如果要将表单视为全局变量,则需要将其声明为全局变量,将应用程序指定为其所有者,并让应用程序在关闭时清理它。
  • 我刚刚注意到您问题的变化。正如 David 所提到的,您可以将 MainForm 设置为所有者,并让它为您而不是应用程序清理内容,这会缩短其生命周期。
猜你喜欢
  • 1970-01-01
  • 2018-06-02
  • 2015-07-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-23
  • 2020-08-24
  • 1970-01-01
相关资源
最近更新 更多