【问题标题】:Why code in any unit finalization section of a package is not executed at shut down?为什么在关闭时不执行包的任何单元完成部分中的代码?
【发布时间】:2011-09-27 09:30:02
【问题描述】:

我有一个使用静态链接运行时包以及使用它们的设计时包的应用程序。出于某种原因,任何单元完成部分中的代码都没有在运行时运行(我不知道这是什么时候开始发生的)。

finalization
  ShowMessage('Goodbye');
end.

关闭 Delphi 会显示该消息,但不会在我的应用程序关闭时显示。更奇怪的是,如果我在 ShowMessage 上放置一个断点,它会在那里中断但不执行该行。如果finalization中有多行,则调试器在第一行停止,不执行,然后跳转到最后。

procedure ProcOne;
begin
  SomeObject.Free; // Debugger does not enter or stop here
  SomeObject := nil;
end;

finalization
  ProcOne; // Debugger stops here, doesn't execute, jumps to "end."
  ProcTwo; // Every line has a blue dot
  ShowMessage('Bye');
end.

ProcOne 断点上的调用堆栈显示 @Halt0 => FinalizeUnits => MyPackage.MyUnit.Finalization。

如果我将单元包含在不使用包的应用程序中,一切都会正常执行。

有人知道是什么原因造成的吗?

编辑:

感谢 Allen Bauer 指出正确方向的评论,我已设法隔离问题。如果使用运行时包构建应用程序,然后动态加载也引用该包和单元的另一个包,似乎会出现问题。

我创建了一个演示问题的测试项目:TestFinalization

有谁知道造成这种情况的原因和/或解决方法?在您注意到外部资源没有被清理之前,您通常可能不会注意到您的最终确定没有运行。

【问题讨论】:

  • 您确定ProcOneProcTwo 不会抛出任何异常吗?
  • 是的,任何东西都不会执行。例如。 MyInt := 5;如果断点落地时是3,跨过之后还是3。
  • 另外,在文本编辑器中手动检查您的 .dpk 文件,然后查找:{$WeakPackageUnit ON}
  • 在您的应用程序执行期间,您是否动态加载其他包?这些包是否也引用了相同的问题包/单元?
  • @Allen Bauer:是的,引用包含问题单元的包的其他包在运行时动态加载。但并非所有动态加载的包都会导致问题,即使所有加载的包都引用了静态包。问题不仅在于一个单元,而且任何单元的最终确定中的代码都不会被执行。

标签: delphi package delphi-2007 finalization


【解决方案1】:

确保在关闭之前为每个动态加载的包调用 UnloadPackage。如果您只是调用 UnloadLibrary(或仅依靠操作系统卸载它们),则不会调用该包中的单元和其他包中的所有单元的最终确定。初始化和终结是使用引用计数系统完成的,因为面对动态加载的包,无法知道将初始化哪些单元以及何时初始化。只有当你平衡了终结调用和初始化调用时,最后一次终结调用才会真正执行终结部分中的代码块。同样,只有对初始化部分的第一次调用才会真正执行代码块。

初始化/终结是使用编译器为给定模块生成的表完成的。当您构建与包链接的 exe 或 dll 时,此表包含对实际使用的所有单元的引用,即使是来自链接包的单元。请注意,只有实际引用的单位实际被初始化。 IOW,如果您在 PackageA 中有 100 个单元并且 exe 仅引用其中一个,那么只有该单元及其使用的任何单元将被初始化。

对于动态加载的包,实际上无法知道实际会使用哪些单元,因此编译器会生成 init/finit 表,就好像每个单元都已初始化一样。在调用 LoadLibrary 期间加载包时处理此表,而是通过调用称为 Initialize() 的特殊导出来处理。 LoadPackage 函数确保调用此函数。该表仅确保加载包中的所有单元都已初始化。只有在任何其他包中实际触及的单元被初始化,类似于我上面提到的 exe/dll 案例。 UnloadPackge 执行相反的操作,并在调用 UnloadLibrary() 之前调用特殊的 export Finalize()。

最后,如果您对任何打包单元的使用列表进行了更改并且只重新构建包,您可能会遇到令人困惑的情况,即即使您在给定包中的单元正确“使用”,也可能不会调用初始化/最终化彼此。这是因为 init/finit 是由 加载模块 控制的,而不是来自其自身。只有在使用 LoadPackage 显式加载包的情况下,该包中的每个单元(并且仅该包)才会被初始化/完成。

【讨论】:

  • 感谢您的时间和详细的解释。对于已经被动态加载的包注册的任何类,是否需要先调用 UnRegisterModuleClasses()?
  • 你不应该调用它,不。类将调用 AddModuleUnloadProc,以获取将调用 UnRegisterModuleClasses() 的卸载通知。
  • 只是出于兴趣,我发现 UnloadPackage() 行由于某种原因被注释掉了。在我们的源存档中,这是在 2003 年之前完成的,因此任何人至少花了 8 年时间才注意到最终确定没有运行,现在才注意到它,因为 COM 服务器没有在应用退出时关闭。跨度>
【解决方案2】:

根据 Allen Bauer 的回答,对于与我处于相同情况的任何人:

我有许多单元使用初始化/完成来进行自我注册。本着Raymond Chen's advice 的精神,我只在重要的时候进行注销:

initialization
  RegisterUnit();
finalization
//In debug mode we track memory leaks so properly deregister
//In release mode the app is shutting down; do not waste time
//freeing memory that's going to be freed anyway
{$IFDEF DEBUG}
  UnloadUnit();
{$ENDIF}

我将其中的一堆移动到包中,但这破坏了核心包finalization,正如问题所述。

Allen Bauer's answer 开始,您必须为所有动态加载的包调用UnloadPackage(),否则您将无法在核心中获得正确的终结调用。

但是我不能再使用那个优化了。我必须在完成时痛苦地取消注册每个包,因为一旦我卸载包 DLL,它在核心中注册的对象将成为僵尸。

这感觉像是白费力气。卸货应该很快。我想让所有动态加载的包都挂起,直到一切都被简单地破坏。

您可以做的是在所有动态加载的包上调用FinalizePackage()!这会在保持包加载的同时平衡引用计数器。

如果一个包使用这种“跳过 deinit”技巧,它的对象将保持活动状态,直到进程销毁。 (包的工作是确保可以调用它们的任何东西都不会损坏)。如果没有,它会被完全取消初始化,剩下的是一个惰性 DLL。

这当然不适用于您计划真正动态卸载的包。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多