我有一个使用静态链接的运行时包以及使用它们的设计时包的应用程序。由于某种原因,任何单元定型部分中的代码都没有在运行时运行(我不知道这是什么时候开始发生的。)
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。
如果我将该单元包含在不使用包的应用程序中,则一切都正常执行。
有没有人知道造成这种情况的原因是什么?
编辑:
感谢艾伦鲍尔的评论指向了正确的方向,我设法解决了这个问题。如果使用运行时包构建应用程序,然后动态加载另一个也引用该包和单元的包,则似乎会出现问题。
我创建了一个演示问题的测试项目:TestFinalization
有谁知道这个和/或解决方法的原因?在您注意到外部资源未被清除之前,您通常可能不会注意到您的最终确定没有运行。
答案 0 :(得分:8)
确保在关闭之前为每个动态加载的包调用UnloadPackage。如果您只是调用UnloadLibrary(或者只是依靠操作系统来卸载它们),那么就不会调用该包中的单元和其他包中的所有单元的最终结果。使用引用计数系统完成初始化和完成,因为面对动态加载的包,无法知道将初始化哪些单元以及何时初始化。只有当您使用初始化调用平衡终结调用时,最后的终结调用才会实际执行完成部分中的代码块。同样,只有第一次调用初始化部分才会实际执行代码块。
使用针对给定模块的编译器生成的表来完成初始化/完成。当您构建与包链接的exe或dll时,此表包含对实际使用的所有单元的引用,甚至包括来自链接包的那些单元。请注意,仅实际引用的单位实际上已初始化。 IOW,如果您在PackageA中有100个单位且exe只引用其中一个单位,则只会初始化该单位及其使用的任何单位。
对于动态加载的包,实际上无法知道实际使用哪些单元,因此编译器会生成init / finit表,就好像每个单元都已初始化一样。在调用LoadLibrary期间加载包时,不处理此表,而是通过调用名为Initialize()的特殊导出来处理。 LoadPackage函数确保调用此函数。此表仅确保初始化加载包中的所有单元。只初始化了任何其他包中实际触及的单位,类似于我上面提到的exe / dll情况。 UnloadPackge执行相反的操作,并在调用UnloadLibrary()之前调用特殊导出Finalize()。
最后,如果您对使用任何打包单元的列表进行了更改并且仅重建了包,则可能会遇到令人困惑的情况,即使给定包中的单元正确“使用”,也可能无法调用初始化/终结彼此。这是因为init / finit由加载模块控制,而不是从其自身内部控制。只有在使用LoadPackage显式加载包的情况下,才会初始化/最终确定该包中的每个单元(以及该包)。
答案 1 :(得分:0)
根据艾伦·鲍尔(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。
这当然不适用于您计划真正真正动态卸载的软件包。