Delphi 应用程序的单元终结顺序,使用运行时包编译?

Delphi 应用程序的单元终结顺序,使用运行时包编译?,delphi,delphi-2010,packages,finalization,Delphi,Delphi 2010,Packages,Finalization,我需要在完成SysUtils单元后执行代码 我将代码放在单独的单元中,并将其首先包含在dpr文件的uses子句中,如下所示: project Project1; uses MyUnit, // <- my separate unit SysUtils, Classes, SomeOtherUnits; procedure Test; begin // end; begin SetProc(Test); end. unit MyUnit; interf

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

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

project Project1;

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

procedure Test;
begin
  //
end;

begin
  SetProc(Test);
end.
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的finalization部分将在SysUtils的finalization部分之后执行。这是德尔福的帮助告诉我的

然而,情况并非总是如此

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

一个应用程序按预期运行:Halt0->FinalizeUnits->…其他单元…->SysUtils的最终确定->MyUnit的最终确定->…其他单位…

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

这两个项目的设置非常相似。我试图消除/最小化他们的差异,但我仍然看不到这种行为的原因

我试着调试它,发现:似乎每个单元都有某种引用计数。InitTable似乎包含对同一单元的多个引用。当SysUtils的finalization部分第一次被调用时,它将更改引用计数器,而不执行任何操作。然后执行MyUnit的终结。然后再次调用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
...
Finalization://SysUtils'Finalization
5003B3F0 55推式ebp//此处和下方是某种形式的存根
5003B3F1 8BEC mov ebp,esp
5003B3F333C0异或eax,eax
5003B3F5 55推式ebp
5003B3F6 688EB50350推送$5003b58e
5003B3FB 64FF30推送dword ptr fs:[eax]
5003B3FE 648920 mov fs:[eax],esp
5003B401 FF05DCAD1150 inc dword ptr[$5011addc]//此处:某种参考计数器

5003B407 0F857301000 jnz$5003b580/您不会错过任何东西。这就是正在发生的事情

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


您的MyUnit需要在SysUtils之后完成,有什么特别的原因吗?

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

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

一般规则:

  • 较低级别的东西应该在较高级别的东西之前初始化
  • 终结应按与初始化相反的顺序进行
这些规则几乎总是有道理的。上级单位的初始化通常依赖于下级单位提供的服务。例如,如果没有SysUtils,Delphi中就没有异常支持。相反顺序的终结也有同样的意义:高级终结依赖于低级单位提供的服务,因此它们必须在低级单位终结之前运行


综上所述,关于您的问题,如果您说的是真的,那么听起来编译器或RTL中可能有一个bug:主EXE首先使用
MyUnit
,并且
MyUnit
在其接口或实现中不使用其他单元,动态加载的包也没有什么有趣的事情发生。我所能建议的就是不断减少这个项目的古怪行为,直到你有一个最小的复制样本;在这一点上,应该清楚地知道问题的确切原因。

我不确定,但是否仍然存在良好的旧ExitProc全局变量Turbo/BorlandPascal fame?如果是的话,这可以解决你的问题。

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

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

就是这样:

DLL的系统->DLL的系统->exe的系统(跳过)->我的单位->exe的系统(跳过)->等

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

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

(哦,我多么希望有一台时光机能够回到过去,并在MMShutdown事件之前强制某人添加:D)

>是否有