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

发布于 2024-08-28 11:46:38 字数 2092 浏览 8 评论 0原文

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

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

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 ->最终确定单位 -> ...其他单位... -> SysUtils的最终确定-> MyUnit 的最终确定 -> ...其他单位...

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

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

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

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

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

I need to execute my code after finalization of SysUtils unit.

I've placed my code in separate unit and included it first in uses clause of dpr-file, like this:

project Project1;

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

procedure Test;
begin
  //
end;

begin
  SetProc(Test);
end.

MyUnit looks like this:

unit MyUnit;

interface

procedure SetProc(AProc: TProcedure);

implementation

var
  Test: TProcedure;

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

initialization

finalization
  Test;
end.

Note that MyUnit doesn't have any uses.

This is usual Windows exe, no console, without forms and compiled with default run-time packages. MyUnit is not part of any package (but I've tried to use it from package too).

I expect that finalization section of MyUnit will be executed after finalization section of SysUtils. This is what Delphi's help tells me.

However, this is not always the case.

I have 2 test apps, which differs a bit by code in Test routine/dpr-file and units, listed in uses. MyUnit, however, is listed first in all cases.

One application is run as expected: Halt0 -> FinalizeUnits -> ...other units... -> SysUtils's finalization -> MyUnit's finalization -> ...other units...

But the second is not. MyUnit's finalization is invoked before SysUtils's finalization. The actual call chain looks like this: Halt0 -> FinalizeUnits -> ...other units... -> SysUtils's finalization (skipped) -> MyUnit's finalization -> ...other units... -> SysUtils's finalization (executed)

Both projects have very similar settings. I tried a lot to remove/minimize their differences, but I still do not see a reason for this behaviour.

I've tried to debug this and found out that: it seems that every unit have some kind of reference counting. And it seems that InitTable contains multiply references to the same unit. When SysUtils's finalization section is called first time - it change reference counter and do nothing. Then MyUnit's finalization is executed. And then SysUtils is called again, but this time ref-count reaches zero and finalization section is executed:

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

Can anyone can shred light on this issue? Am I missing something?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

甜扑 2024-09-04 11:46:38

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

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

一般规则:

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

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

综上所述,就您的问题而言,如果您所说的是真的,听起来编译器或 RTL 中的某个地方可能存在错误:主 EXE 首先使用 MyUnit ,并且 MyUnit 在其接口或实现中不使用其他单元,并且动态加载的包不会发生任何有趣的事情。我所能建议的就是继续削减具有奇怪行为的项目,直到获得最小的可复制样本;到那时,应该清楚到底是什么导致了问题。

Units are finalized in reverse order of initialization. The order of initialization is determined by a non-cyclic (i.e. never descends into an already-visited unit) post-order traversal of the unit uses graph, starting with the main uses clause (in the program or library). SysInit is normally the first unit to be initialized, followed by System.

Dynamic loading of packages complicates things, because the main EXE or DLL gets to specify the order of initialization of the units used by the main image. So when a package is dynamically loaded, it will run what it thinks should be the initialization order, but units already initialized will be skipped; when the package is dynamically unloaded, this happens in reverse.

The general rules:

  • lower-level things should be initialized before higher-level things
  • finalization should be in reverse order of initialization

These rules almost always make sense. Higher-level units' initializations often rely on services provided by lower-level units. For example, without SysUtils, there is no exception support in Delphi. Reverse order finalization makes sense for the same reason: high-level finalizations rely on services provided by lower-level units, so they must run before the lower-level units' finalizations.

All that said, with respect to your problem, it sounds like there may be a bug somewhere in the compiler or RTL, if what you say is true: that the main EXE uses MyUnit first, and MyUnit uses no other units in its interface or implementation, and there's no funny business going on with dynamically loaded packages. All I can suggest is to keep paring down the project with the odd behaviour until you have a minimal reproducing sample; at that point, it should be clear exactly what is causing the problem.

强辩 2024-09-04 11:46:38

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

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

就是这样:

DLL 的系统 -> DLL 的 SysUtils -> exe的系统(跳过)-> MyUnit -> exe 的 SysUtils (已跳过)->等

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

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

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

I was able to find a reason and I feel myself a bit stupid now :)

My second test application have a static reference to DLL, which was compiled with RTL.bpl (it's empty, except for references to SysUtils and having 1 simple routine). So, since DLL is statically linked, it is initialized before any code from exe have chances to run.

That's it:

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

Finalizations are in reverse order, leading to execution of MyUnit before SysUtils.

Solution: require to include MyUnit first in all projects.

(oh, how I wish to have a time-machine to travel back in time and force somebody to add OnBeforeMMShutdown event :D )

时光瘦了 2024-09-04 11:46:38

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

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

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

You're not missing anything. That's exactly what's happening.

When your program loads a package, it will initialize all the units used in that package. When it unloads the package, it has to finalize all the units. But finalization often involves freeing variables. Remember that double-frees are a Bad Thing, especially if they happen during finalization when certain exception-handling features might have been unloaded, making debugging very difficult. So it puts a reference counter on the unit initializations so that they don't get finalized until everything that was using them is done with them.

Is there any particular reason why your MyUnit needs to finalize after SysUtils?

神经暖 2024-09-04 11:46:38

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

I am not sure but isn't there still the good old ExitProc global variable of Turbo/BorlandPascal fame? If yes, this could solve your problem.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文