Ред за финализиране на модул за приложение, компилирано с пакети по време на изпълнение?

Трябва да изпълня кода си след финализиране на модула SysUtils.

Поставих кода си в отделна единица и го включих първо в клаузата uses на 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 няма никакви приложения.

Това е обикновен Windows exe, без конзола, без формуляри и компилиран с пакети за изпълнение по подразбиране. MyUnit не е част от нито един пакет (но се опитах да го използвам и от пакет).

Очаквам, че секцията за финализиране на MyUnit ще бъде изпълнена след секцията за финализиране на SysUtils. Това ми казва помощта на Delphi.

Това обаче не винаги е така.

Имам 2 тестови приложения, които се различават малко по кода в Тестова рутина/dpr-файл и единици, изброени в употреби. MyUnit обаче е посочен първи във всички случаи.

Едно приложение се изпълнява според очакванията: Halt0 -> FinalizeUnits -> ...други модули... -> Финализиране на SysUtils -> Финализиране на MyUnit -> ...други модули...

Но второто не е така. Финализирането на MyUnit се извиква преди финализирането на SysUtils. Действителната верига на повикванията изглежда така: Halt0 -> FinalizeUnits -> ...други модули... -> Финализиране на 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
...

Може ли някой да хвърли светлина по този въпрос? Изпускам ли нещо?


person Alex    schedule 13.04.2010    source източник


Отговори (4)


Единиците се финализират в обратен ред на инициализацията. Редът на инициализация се определя от нециклично (т.е. никога не се спуска във вече посетена единица) преминаване след поръчка на графиката за използване на единица, започвайки с клаузата за главните употреби (в програмата или библиотеката). SysInit обикновено е първата единица, която се инициализира, последвана от System.

Динамичното зареждане на пакети усложнява нещата, тъй като основният EXE или DLL трябва да укаже реда на инициализация на единиците, използвани от основното изображение. Така че, когато пакет се зарежда динамично, той ще изпълнява това, което смята, че трябва да бъде редът за инициализация, но вече инициализираните единици ще бъдат пропуснати; когато пакетът се разтоварва динамично, това се случва в обратен ред.

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

  • нещата от по-ниско ниво трябва да се инициализират преди нещата от по-високо ниво
  • финализирането трябва да е в обратен ред на инициализацията

Тези правила почти винаги имат смисъл. Инициализациите на единици от по-високо ниво често разчитат на услуги, предоставени от единици от по-ниско ниво. Например, без SysUtils няма поддръжка за изключения в Delphi. Финализирането на обратен ред има смисъл по същата причина: финализирането на високо ниво разчита на услуги, предоставени от единици на по-ниско ниво, така че те трябва да се изпълняват преди финализирането на единиците на по-ниско ниво.

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

person Barry Kelly    schedule 13.04.2010
comment
Всички пакети са статично свързани и няма динамично зареждане - това е сигурно. Благодаря за отговора, но се страхувах от това :( Надявах се, че някой може да даде няколко съвета за търсене на конкретни неща, защото не знам къде да търся другаде. Ще опитам още няколко пъти... - person Alex; 13.04.2010

Успях да намеря причина и сега се чувствам малко глупав :)

Моето второ тестово приложение има статична препратка към DLL, която е компилирана с RTL.bpl (тя е празна, с изключение на препратките към SysUtils и има 1 проста рутина). Така че, тъй като DLL е статично свързан, той се инициализира, преди който и да е код от exe да има шанс да се изпълни.

Това е:

System на DLL -> SysUtils на DLL -> System на exe (пропуснато) -> MyUnit -> на exe SysUtils(пропуснато) -> и т.н

Финализирането е в обратен ред, което води до изпълнение на MyUnit преди SysUtils.

Решение: изисквайте първо да включите MyUnit във всички проекти.

(о, как ми се иска да имам машина на времето, която да пътува назад във времето и да принуди някого да добави събитие OnBeforeMMShutdown :D )

person Alex    schedule 20.04.2010

Нищо не ти липсва. Точно това се случва.

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

Има ли някаква конкретна причина, поради която вашият MyUnit трябва да се финализира след SysUtils?

person Mason Wheeler    schedule 13.04.2010
comment
› Има ли някаква конкретна причина, поради която вашият MyUnit трябва да се финализира след SysUtils? да Това са чекове за изтичане на памет. - person Alex; 13.04.2010
comment
@Alexander: Прави ли нещо специално, което не можете да получите с FastMM в FullDebugMode? - person Mason Wheeler; 13.04.2010
comment
› Прави ли нещо специално, което не можете да получите с FastMM в FullDebugMode? Това не е въпросът, който задавам, така че нека го оставим настрана. FastMM не работи във второто ми приложение точно по същата причина: нарича се твърде рано. - person Alex; 13.04.2010
comment
(като превантивен коментар: изобщо не се опитвам да използвам FastMM, така че не предлагайте AQTime или нещо подобно - просто се опитах да използвам FastMM като допълнителна проверка, дали съм напълно глупав или не; първоначалният проблем е неочаквано изпълнение ред; опитвам се да разбера какво е и да видя дали мога да го контролирам) - person Alex; 13.04.2010

Не съм сигурен, но все още ли не съществува добрата стара глобална променлива на ExitProc на славата на Turbo/BorlandPascal? Ако да, това може да реши проблема ви.

person dummzeuch    schedule 13.04.2010
comment
Да, така е. За съжаление, тази процедура се извиква преди да извика която и да е секция за финализиране. Така че, този е твърде рано. Има и събитие ExitProcess, но то се извиква точно преди прекратяването на процеса - твърде късно. (Аз не пиша мениджър на паметта - инсталирам само филтър; така че за мен е важно да изпълня кода си преди финализирането на системата, когато мениджърът на паметта по подразбиране може да освободи цялата памет) По дяволите, мисля да инсталирам кука на финализирането на системата :( Просто шегувам се - person Alex; 14.04.2010
comment
@Alexander: Може би кука на финализирането на SysUtils не е толкова лоша идея, ако не можете да го разрешите по друг начин. - person dummzeuch; 14.04.2010