5

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

finalization
  ShowMessage('Goodbye');
end.

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

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

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

4

2 回答 2

10

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

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

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

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

于 2011-09-28T17:19:45.113 回答
0

对于与我处境相同的任何人,根据艾伦鲍尔的回答:

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

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 的回答,您必须调用UnloadPackage()所有动态加载的包,否则您将无法在核心中获得正确的终结调用。

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

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

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

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

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

于 2019-10-08T10:33:54.430 回答