应用程序的单元最终确定顺序,使用运行时包编译?

时间:2010-04-13 10:45:52

标签: delphi delphi-2010 packages finalization

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

我已将我的代码放在单独的单元中,并将其首先包含在dpr-file的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 的最终化部分之后执行。这就是德尔福的帮助告诉我的。

然而,并非总是如此。

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

按预期运行一个应用程序: Halt0 - &gt; FinalizeUnits - &gt; ......其他单位... - &gt; SysUtils的定稿 - &gt; MyUnit的最终定稿 - &gt; ......其他单位......

但第二个不是。在 SysUtils的最终确定之前,调用了MyUnit的最终化。实际的调用链如下所示: Halt0 - &gt; FinalizeUnits - &gt; ......其他单位... - &gt; SysUtils的定稿(跳过) - &gt; MyUnit的最终定稿 - &gt; ......其他单位... - &gt; SysUtils的定稿(已执行)

两个项目都有非常相似的设置。我尝试了很多来消除/减少他们的差异,但我仍然没有看到这种行为的原因。

我试图对此进行调试并发现:似乎每个单元都有某种引用计数。似乎InitTable包含对同一单元的多次引用。当第一次调用SysUtils的终结部分时 - 它会更改引用计数器而不执行任何操作。然后执行MyUnit的终结。然后再次调用SysUtils,但这次ref-count达到零并执行完成部分:

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
...

任何人都可以轻视这个问题吗?我错过了什么吗?

4 个答案:

答案 0 :(得分:10)

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

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

一般规则:

  • 低层事物应该在更高层次的事情之前进行初始化
  • 终止应该与初始化的顺序相反

这些规则几乎总是有意义的。较高级别单位的初始化通常依赖于较低级别单位提供的服务。例如,没有SysUtils,Delphi中没有异常支持。由于同样的原因,反向订单终结也是有意义的:高级终结依赖于较低级别单位提供的服务,因此它们必须在较低级别单位的最终确定之前运行。

所有这一切,就你的问题而言,听起来在编译器或RTL中某处可能存在错误,如果你说的是真的:主EXE首先使用MyUnit,{{{ 1}}在其接口或实现中不使用其他单元,并且动态加载的包没有可笑的业务。我所能建议的是用奇怪的行为继续削减项目,直到你有一个最小的再现样本;那时,应该清楚究竟是什么导致了这个问题。

答案 1 :(得分:1)

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

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

就是这样:

DLL的系统 - &gt; DLL的 SysUtils - &gt; exe的系统(跳过) - &gt; MyUnit - &gt; exe的 SysUtils (跳过) - &gt;等

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

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

(哦,我希望有一个时间机器回到过去并强迫某人添加OnBeforeMMShutdown事件:D)

答案 2 :(得分:0)

你没有遗漏任何东西。这正是发生的事情。

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

您的MyUnit需要在SysUtils之后完成任何特定原因吗?

答案 3 :(得分:0)

我不确定但是还不是Turbo / BorlandPascal成名的旧的ExitProc全局变量吗?如果是,这可以解决您的问题。