Мога ли да гарантирам изпълнението на персонализиран код за финализиране СЛЕД унищожаването на формуляра?

Имам многонишково приложение с много формуляри, но трябва да инстанцирам някои класове и да извикам някои неща за инициализация преди създаването на формулярите. Разбира се, трябва да изпълня съответния код за финализиране.

Ето опростен пример за .dpr файл:

begin  // .dpr project file
  LoadDlls;
  try
    Config := TConfig.Create;
    try
      Application.Initialize;
      Application.Title := 'Foo';
      Application.CreateForm(TMainForm, MainForm);
      Application.CreateForm(TOtherForm, OtherForm);
      //...other forms...
      Application.Run;
    finally
      Config.Free;
    end;
  finally
    UnloadDlls;
  end;
end;

Проблемът тук е, че кодът вътре в finally блока се изпълнява ПРЕДИ OnDestroy / destructors на моите формуляри. Това става ясно, ако погледнете раздела finalization на Form единица:

finalization
  if Application <> nil then DoneApplication;

И DoneApplication извиква Application.DestroyComponents, което на практика освобождава всички формуляри, притежавани от Application.

Така че формулярите, създадени с Application.CreateForm, ще бъдат унищожени след всеки код в главния блок begin..end.

Това, което искам, е след Application.Run всички формуляри да бъдат унищожени, така че техните OnDestroy манипулатори на събития да могат да видят Config обекта и външните функции, дефинирани в моите dlls. Същото, ако се повдигне изключение. Но също така искам да имам стандартна обработка на изключенията на приложението, ако Config.Free или UnlodDlls се повдигнат (Приложението все още трябва да съществува).

Забележи, че:

  • Бих предпочел да не използвам блок finalization (би ли било възможно в .dpr?), за да поддържам кода по-ясен и с възможност за отстраняване на грешки;
  • Засега предпочитам да не променям твърде много код (напр. динамично създаване на формуляри)

Мисля, че най-простото решение е изрично да се извика Application.DestroyComponents след Application.Run. Мислите ли, че има някакви недостатъци? Има ли по-елегантно решение?

Благодаря ти


person yankee    schedule 10.09.2013    source източник


Отговори (2)


Най-чистият начин да постигнете това, което искате, е да контролирате унищожаването на формите.

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

Можете да напишете вашия .dpr код така:

begin 
  LoadDlls;
  try
    Config := TConfig.Create;
    try
      Application.Initialize;
      Application.Title := 'Foo';
      Application.CreateForm(TMainForm, MainForm);
      try
        OtherForm := TOtherForm.Create(MainForm);
        YetAnotherForm := TYetAnotherForm.Create(MainForm);
        Application.Run;
      finally
        FreeAndNil(MainForm); 
        // will destroy the other forms since they are owned by the main form
      end;
    finally
      Config.Free;
    end;
  finally
    UnloadDlls;
  end;
end;

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

person David Heffernan    schedule 10.09.2013
comment
Благодаря ви, разбирам мнението ви. Бих искал да направя тази промяна като стъпка 2, защото се нуждае от промяна в съзнанието на всички членове на екипа ;-) Засега трябва да гарантирам, че ако някой добави формуляр по стандартния начин, той ще бъде обработен правилно. Защо не Application.DestroyComponents? Що се отнася до dlls, прав си, но има други структури в LoadDlls, които искам правилно да освободя, за да уловя някои течове на памет в основния код. - person yankee; 10.09.2013
comment
Системата ще възстанови паметта при спиране на процеса. Не можете да изтечете в този момент. Вероятно сте достатъчно сигурни, за да се обадите на DestroyComponents в този момент. Бих го направил както трябва, ако бях на твое място, но тогава не знам колко влияние имаш в екипа си! - person David Heffernan; 10.09.2013

Друга опция е да не позволявате на вашите формуляри имплицитно да се позовават на глобалната конфигурация.
Направете зависимостта изрична, като дадете на всеки формуляр собствена препратка към IConfig интерфейс.
Когато RefCount на реферирания екземпляр падне до нула (след всички форми които го използват са били унищожени) може да се самоунищожи.

Правенето на явна зависимост на вашия формуляр (и други обекти) от конфигурацията ще осигури други предимства.

  • Ще бъде много по-лесно да се тества.
  • Формулярите, които не се нуждаят от IConfig, няма да имат зависимостта и няма да се интересуват по никакъв начин.
  • Следователно тези форми ще могат лесно (и очевидно) да се преместват в други приложения с малко по-различна рамка.
person Disillusioned    schedule 10.09.2013
comment
Ще се задълбоча и в този вариант, много е интересен. - person yankee; 11.09.2013