Могу ли я гарантировать выполнение пользовательского кода финализации ПОСЛЕ уничтожения формы?

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

Вот упрощенный пример файла .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/destructor моих форм. Это становится очевидным, если посмотреть на секцию finalization блока Form:

finalization
  if Application <> nil then DoneApplication;

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

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

Я хочу, чтобы после Application.Run все формы были уничтожены, чтобы их OnDestroy обработчики событий могли видеть объект Config и внешние функции, определенные в моих dll. То же самое, если возникает исключение. Но я также хочу иметь стандартную обработку исключений приложения, если 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? Что касается dll, вы правы, но в 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