Порядок завершения модуля для приложения, скомпилированный с пакетами времени выполнения? - PullRequest
6 голосов
/ 13 апреля 2010

Мне нужно выполнить мой код после завершения работы модуля SysUtils.

Я поместил свой код в отдельный модуль и включил его первым в предложение использования файла dpr, например:

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 не используется.

Это обычный exe-файл Windows, без консоли, без форм и скомпилированный с запуском по умолчаниюПакетыMyUnit не является частью какого-либо пакета (но я также пытался использовать его из пакета).

Я ожидаю, что раздел финализации MyUnit будет выполнен после раздела финализации SysUtils .Это то, что подсказывает мне Delphi.

Однако это не всегда так.

У меня есть 2 тестовых приложения, которые немного различаются по коду в подпрограмме Test / dpr-file и единицах измерения, перечисленные в использовании.MyUnit, однако, указан первым во всех случаях.

Одно приложение запускается, как и ожидалось: Halt0 -> FinalizeUnits -> ... другие модули ... -> Завершение SysUtils -> Завершение MyUnit -> ... другие модули ...

Но второго нет.Финализация MyUnit вызывается до финализации SysUtils.Фактическая цепочка вызовов выглядит следующим образом: Halt0 -> FinalizeUnits -> ... другие модули ... -> Завершение SysUtils (пропущено) -> Завершение MyUnit -> ... другие модули ... -> Завершение 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 ]

10 голосов
/ 13 апреля 2010

Единицы завершаются в обратном порядке инициализации. Порядок инициализации определяется нециклическим (то есть никогда не сходящимся в уже посещенным модулем) постпорядковым обходом модуля использует граф, начиная с основного предложения использования (в программе или библиотеке). SysInit обычно является первым инициализируемым модулем, за которым следует System.

Динамическая загрузка пакетов усложняет ситуацию, потому что основной EXE или DLL получает возможность указать порядок инициализации модулей, используемых основным образом. Поэтому, когда пакет загружается динамически, он запускает то, что он считает порядком инициализации, но уже инициализированные модули будут пропущены; когда пакет выгружается динамически, это происходит в обратном порядке.

Общие правила:

  • вещи более низкого уровня должны быть инициализированы перед вещами более высокого уровня
  • Завершение должно быть в обратном порядке инициализации

Эти правила почти всегда имеют смысл. Инициализация модулей более высокого уровня часто зависит от услуг, предоставляемых модулями более низкого уровня. Например, без SysUtils в Delphi нет поддержки исключений. Финализация в обратном порядке имеет смысл по той же причине: высокоуровневые финализации основаны на услугах, предоставляемых юнитами более низкого уровня, поэтому они должны выполняться до финализаций юнитов более низкого уровня.

Все, что сказано, относительно вашей проблемы, звучит так, как будто где-то в компиляторе или RTL может быть ошибка, если то, что вы говорите, верно: сначала в основном EXE-файле MyUnit, а MyUnit нет никаких других модулей в его интерфейсе или реализации, и с динамически загружаемыми пакетами не происходит никаких забавных дел. Все, что я могу предложить, это продолжать анализировать проект со странным поведением, пока у вас не будет минимального воспроизводимого образца; в этот момент должно быть ясно, что именно является причиной проблемы.

1 голос
/ 20 апреля 2010

Мне удалось найти причину, и теперь я чувствую себя немного глупо:)

У моего второго тестового приложения есть статическая ссылка на DLL, которая была скомпилирована с помощью RTL.bpl (она пустая, за исключением ссылок на SysUtils и имеет 1 простую подпрограмму). Таким образом, поскольку DLL статически связана, она инициализируется до того, как какой-либо код из exe сможет запустить.

Вот и все:

DLL System -> DLL SysUtils -> exe's System (пропущено) -> MyUnit -> exe's SysUtils (пропущено) -> и т. Д.

Завершения выполняются в обратном порядке, что приводит к выполнению MyUnit перед SysUtils.

Решение: требуется сначала включить MyUnit во всех проектах.

(о, как бы я хотел, чтобы машина времени возвращалась во времени и заставляла кого-то добавлять событие OnBeforeMMShutdown: D)

0 голосов
/ 13 апреля 2010

Я не уверен, но разве нет старой доброй глобальной переменной ExitProc известности Turbo / BorlandPascal? Если да, это может решить вашу проблему.

0 голосов
/ 13 апреля 2010

Ты ничего не пропустил. Это именно то, что происходит.

Когда ваша программа загружает пакет, она инициализирует все модули, используемые в этом пакете. Когда он выгружает пакет, он должен завершить все модули. Но финализация часто включает освобождение переменных. Помните, что двойные освобождения - это плохая вещь, особенно если они происходят во время финализации, когда некоторые функции обработки исключений могли быть выгружены, что делает отладку очень сложной. Таким образом, он устанавливает счетчик ссылок на инициализации модулей, чтобы они не завершались до тех пор, пока все , использовавшее их, не будет сделано с ними.

Есть ли какая-либо конкретная причина, по которой ваш MyUnit должен завершиться после SysUtils?

...