我能否保证自定义终结代码在窗体销毁之后执行?

4

我有一个多线程应用程序,其中包含许多表单,但在创建这些表单之前,我必须实例化一些类并调用一些初始化内容。当然,我也必须执行相应的终止代码。

以下是一个简化的 .dpr 文件示例:

begin  // .dpr project file
  LoadDlls;
  try
    Config := TConfig.Create;
    try
      Application.Initialize;
      Application.Title := 'Foo';
      Application.CreateForm(TMainForm, MainForm);
      Application.CreateForm(TOtherForm, OtherForm);
      //...other forms...
      Application.Run;
    finally
      Config.Free;
    end;
  finally
    UnloadDlls;
  end;
end;

这里的问题在于finally块内的代码会在我的窗体的OnDestroy / 析构函数之前执行。这一点可以从Form单元的finalization部分清晰地看出来:
finalization
  if Application <> nil then DoneApplication;

DoneApplication调用Application.DestroyComponents,这将有效地释放所有属于Application的窗体。

因此,使用Application.CreateForm创建的窗体将在主begin..end块内的任何代码之后被销毁。

我想要的是,在Application.Run之后,所有窗体都会被销毁,这样它们的OnDestroy事件处理程序就可以访问Config对象和我在dll中定义的外部函数。如果发生异常,也是如此。 但是,如果Config.FreeUnlodDlls引发异常,我还希望具有标准应用程序的异常处理(应用程序仍然必须存在)。

请注意:

  • 我更喜欢不使用finalization块(在.dpr中是否可能?)以保持代码更清晰、更易于调试;
  • 目前,我不想改变太多的代码(例如动态创建窗体)。

我认为最简单的解决方案是在Application.Run之后显式调用Application.DestroyComponents。你觉得有什么缺点吗?还有更优雅的解决方案吗?

谢谢。

2个回答

6
实现你想要的最干净的方法,是通过控制表单的销毁来实现。
唯一需要由“Application”拥有的表单是你的主表单。这是必须的,因为调用“Application.CreateForm”创建的第一个表单被指定为主表单。因此,我的建议是你只需调用一次“Application.CreateForm”来创建主表单。对于其他所有表单,请通过调用它们的构造函数来创建它们。让其他表单由主表单拥有。在关闭时,销毁主表单,并让它带着所有拥有的表单一起离开。
你可以像下面这样编写你的.dpr代码:
begin 
  LoadDlls;
  try
    Config := TConfig.Create;
    try
      Application.Initialize;
      Application.Title := 'Foo';
      Application.CreateForm(TMainForm, MainForm);
      try
        OtherForm := TOtherForm.Create(MainForm);
        YetAnotherForm := TYetAnotherForm.Create(MainForm);
        Application.Run;
      finally
        FreeAndNil(MainForm); 
        // will destroy the other forms since they are owned by the main form
      end;
    finally
      Config.Free;
    end;
  finally
    UnloadDlls;
  end;
end;

另外一个要说明的点是,也许你不需要卸载DLL。由于这显然是一个可执行文件,系统会自动卸载它们。为什么需要这样做呢?


谢谢,我理解你的观点。我想把这个更改作为“第二步”,因为它需要团队所有成员的认同;-)目前我必须确保如果有人“按照标准方式”添加表单,它将被正确处理。Application.DestroyComponents为什么不行呢?至于dll文件,你是对的,但是在LoadDlls中还有其他结构我想要正确地释放以便能够捕捉到主代码中的一些内存泄漏。 - yankee
系统将在进程关闭时回收内存。此时您不能泄漏内存。在这一点上,您可能足够安全地调用DestroyComponents。如果我是你,我会做得更好,但我不知道你在团队中有多少影响力! - David Heffernan

4
另一个选择是不要让您的表单隐式引用全局配置。
通过为每个表单提供自己的对IConfig接口的引用来明确依赖关系。
当所引用实例的RefCount降至零(在使用它的所有表单都被销毁后)时,它可以自我销毁。
使您的表单(以及其他对象)对配置的依赖性变得明确将提供其他好处。
  • 这样测试将更容易。
  • 不需要IConfig的表单将没有此依赖项,并且无论如何都不会关心。
  • 因此,这些表单将很容易(并且显然)移动到具有稍微不同框架的其他应用程序中。

我也会深入研究这个选项,它非常有趣。 - yankee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接