【问题标题】:Unit finalization order for application, compiled with run-time packages?应用程序的单元完成顺序,使用运行时包编译?
【发布时间】:2011-02-07 09:39:49
【问题描述】:

我需要在完成 SysUtils 单元后执行我的代码。

我已将我的代码放在单独的单元中,并首先将其包含在 dpr 文件的 uses 子句中,如下所示:

project Project1;

uses
  MyUnit,    // <- my separate unit
  SysUtils,
  Classes,
  SomeOtherUnits;

procedure Test;
begin
  //
end;

begin
  SetProc(Test);
end.

MyUnit 如下所示:

unit MyUnit;

interface

procedure SetProc(AProc: TProcedure);

implementation

var
  Test: TProcedure;

procedure SetProc(AProc: TProcedure);
begin
  Test := AProc;
end;

initialization

finalization
  Test;
end.

请注意,MyUnit 没有任何用途。

这是通常的 Windows exe,没有控制台,没有表单,并使用默认运行时包编译。 MyUnit 不是任何包的一部分(但我也尝试从包中使用它)。

我希望 MyUnit 的终结部分将在 SysUtils 的终结部分之后执行。这是 Delphi 的帮助告诉我的。

但是,情况并非总是如此。

我有 2 个测试应用程序,它们在使用中列出的测试例程/dpr 文件和单元中的代码略有不同。然而,MyUnit 在所有情况下都排在第一位。

一个应用程序按预期运行:Halt0 -> FinalizeUnits -> ...其他单元... -> SysUtils 的完成 -> MyUnit 的完成 -> ...其他单元...

但第二个不是。 MyUnit 的终结在SysUtils 的终结之前调用。实际的调用链如下所示:Halt0 -> FinalizeUnits -> ...其他单元... -> SysUtils 的终结(跳过)-> MyUnit 的终结 -> ...其他单元... -> SysUtils 的终结(已执行)

这两个项目的设置非常相似。我尝试了很多来消除/最小化它们的差异,但我仍然看不出这种行为的原因。

我尝试对此进行调试并发现:似乎每个单元都有某种引用计数。似乎 InitTable 包含对同一单元的多次引用。当 SysUtils 的 finalization 部分第一次被调用时——它改变了引用计数器并且什么都不做。然后执行 MyUnit 的 finalization。然后再次调用 SysUtils,但这一次 ref-count 达到零并执行 finalization 部分:

Finalization: // SysUtils' finalization
5003B3F0 55               push ebp          // here and below is some form of stub
5003B3F1 8BEC             mov ebp,esp
5003B3F3 33C0             xor eax,eax
5003B3F5 55               push ebp
5003B3F6 688EB50350       push $5003b58e
5003B3FB 64FF30           push dword ptr fs:[eax]
5003B3FE 648920           mov fs:[eax],esp
5003B401 FF05DCAD1150     inc dword ptr [$5011addc] // here: some sort of reference counter
5003B407 0F8573010000     jnz $5003b580     // <- this jump skips execution of finalization for first call
5003B40D B8CC4D0350       mov eax,$50034dcc // here and below is actual SysUtils' finalization section
...

谁能解释一下这个问题?我错过了什么吗?

【问题讨论】:

    标签: delphi delphi-2010 packages finalization


    【解决方案1】:

    我找到了一个理由,我现在觉得自己有点傻:)

    我的第二个测试应用程序有一个对 DLL 的静态引用,它是用 RTL.bpl 编译的(它是空的,除了对 SysUtils 的引用并有 1 个简单的例程)。因此,由于 DLL 是静态链接的,它在任何来自 exe 的代码有机会运行之前被初始化。

    就是这样:

    DLL 的 System -> DLL 的 SysUtils -> exe 的 System(跳过)-> MyUnit -> exe 的SysUtils(跳过)-> 等

    终结顺序相反,导致 MyUnit 在 SysUtils 之前执行。

    解决方案:要求在所有项目中首先包含 MyUnit。

    (哦,我多么希望有一台时光机可以回到过去并强迫某人添加 OnBeforeMMShutdown 事件:D)

    【讨论】:

      【解决方案2】:

      我不确定,但Turbo/BorlandPascal 名声中的旧ExitProc 全局变量是否仍然存在?如果是,这可以解决您的问题。

      【讨论】:

      • 是的。不幸的是,该例程在调用任何终结部分之前 被调用。所以,这个还为时过早。还有 ExitProcess 事件,但它在进程终止之前被调用 - 为时已晚。 (我不写内存管理器——只安装一个过滤器;所以当默认内存管理器可能释放所有内存时,在系统完成之前执行我的代码对我来说很重要)哎呀,我正在考虑在系统的完成时安装钩子:(只是开玩笑。
      • @Alexander:如果您无法以其他方式解决它,那么也许在 SysUtils 最终确定上挂一个钩子并不是一个坏主意。
      【解决方案3】:

      单元以与初始化相反的顺序完成。初始化的顺序由单元使用图的非循环(即永远不会下降到已访问的单元)后序遍历确定,从主要使用子句(在程序或库中)开始。 SysInit 通常是第一个被初始化的单元,然后是 System。

      包的动态加载使事情变得复杂,因为主 EXE 或 DLL 可以指定主映像使用的单元的初始化顺序。所以当一个包被动态加载时,它会按照它认为应该是初始化的顺序运行,但是已经初始化的单元将被跳过;当包被动态卸载时,情况正好相反。

      一般规则:

      • 低级事物应该在高级事物之前初始化
      • 完成的顺序应该与初始化相反

      这些规则几乎总是有意义的。高层单元的初始化通常依赖于低层单元提供的服务。例如,没有 SysUtils,Delphi 中就没有异常支持。出于同样的原因,逆序终结也有意义:高级终结依赖于低级单元提供的服务,因此它们必须在低级单元终结之前运行。

      所有这一切,关于你的问题,听起来编译器或 RTL 中的某个地方可能存在错误,如果你说的是真的:主 EXE 首先使用 MyUnit,然后 MyUnit 使用它的接口或实现中没有其他单元,动态加载的包也没有什么有趣的事情。我所能建议的就是继续用奇怪的行为减少项目,直到你有一个最小的复制样本;到那时,应该清楚到底是什么导致了问题。

      【讨论】:

      • 所有包都是静态链接的,没有动态加载——这是肯定的。谢谢你的回答,但我很害怕:(我希望有人能给出一些提示来寻找具体的东西,因为我不知道在哪里看。我会再试几次......跨度>
      【解决方案4】:

      你没有错过任何东西。这正是正在发生的事情。

      当您的程序加载一个包时,它将初始化该包中使用的所有单元。当它卸载包时,它必须完成所有单元。但最终确定通常涉及释放变量。请记住,双重释放是一件坏事,特别是如果它们发生在完成过程中,此时某些异常处理功能可能已被卸载,从而使调试变得非常困难。因此,它会在单元初始化上放置一个引用计数器,以便在使用它们的所有操作都完成之前,它们不会被最终确定。

      您的 MyUnit 是否需要在 SysUtils 之后完成?

      【讨论】:

      • > 是否有任何特殊原因导致您的 MyUnit 需要在 SysUtils 之后完成?是的。这是内存泄漏检查。
      • @Alexander:在 FullDebugMode 中,FastMM 是不是在做一些特别的事情?
      • > 在 FullDebugMode 中,它是否在做一些您无法在 FastMM 中获得的特殊功能?这不是我要问的问题,所以让我们把它放在一边。出于同样的原因,FastMM 在我的第二个应用程序中不能正常工作:它被称为太早了。
      • (作为先发制人的评论:我根本没有尝试使用 FastMM,所以不要建议使用 AQTime 或其他东西 - 我只是尝试使用 FastMM 作为附加检查,如果我完全愚蠢或不是;最初的问题是意外的执行顺序;我正在尝试了解它是什么,看看我是否可以控制它)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-10
      • 1970-01-01
      • 2021-12-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多