6

我需要在完成 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 的最终化部分之后执行。这是德尔福的帮助告诉我的。

然而,这并非总是如此。

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

一个应用程序按预期运行:Halt0 -> FinalizeUnits -> ...其他单元... -> SysUtils's finalization -> MyUnit's finalization -> ...其他单元...

但第二个不是。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
...

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

4

4 回答 4

10

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

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

一般规则:

  • 低级事物应该在高级事物之前初始化
  • 终结应该是初始化的相反顺序

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

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

于 2010-04-13T12:26:39.270 回答
1

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

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

而已:

DLL's System -> DLL's SysUtils -> exe's System (skipped) -> MyUnit -> exe's SysUtils (skipped) -> etc

最终确定的顺序相反,导致 MyUnit 在 SysUtils 之前执行。

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

(哦,我多么希望有一台时光机能够及时返回并强迫某人添加 OnBeforeMMShutdown 事件:D)

于 2010-04-20T13:12:33.677 回答
0

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

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

是否有任何特殊原因导致您的 MyUnit 需要在 SysUtils 之后完成?

于 2010-04-13T11:30:01.687 回答
0

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

于 2010-04-13T19:57:45.310 回答