Can I guarrantee execution of custom finalization code AFTER form destruction?

534 views Asked by At

I have a multithreaded application with many forms, but I have to instantiate some classes and call some initialization stuff before the creation of the forms. Of course I have to execute the corresponding finalization code.

Here is a simplified example of .dpr file:

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;

The problem here is that the code inside finally blocks get executed BEFORE the OnDestroy / destructors of my forms. This turns clear looking at the finalization section of Form unit:

finalization
  if Application <> nil then DoneApplication;

And DoneApplication calls Application.DestroyComponents which effectively Frees all Application's owned Forms.

So, Forms created with Application.CreateForm will be destroyed after any code inside the main begin..end block.

What I want is that after Application.Run all the forms are destroyed, so that their OnDestroy event handlers can see the Config object and the external functions defined in my dlls. Ditto if an exception is raised. But I also want to have the standard Application's exception handling if Config.Free or UnlodDlls raise (Application must still exist).

Note that:

  • I'd prefer not to use a finalization block (would it be possible in .dpr?) to keep code clearer and debuggable;
  • For now, I prefer not to change too much code (e.g. dynamically create forms)

I think the simplest solution is to explicitly call Application.DestroyComponents after Application.Run. Do you think is there any drawbacks? Is there a more elegant solution?

Thank you

2

There are 2 answers

2
David Heffernan On BEST ANSWER

The cleanest way to achieve what you want is for you to control the destruction of the forms.

The only form that needs to be owned by Application is your main form. That needs to be so because the first form created by a call to Application.CreateForm is designated to be the main form. So, my advice is that you should make one call, and one call only to Application.CreateForm, to create the main form. For all your other forms, create them by calling their constructors. Let the other forms be owned by the main form. When it's time to shut down, destroy the main form, and let it take all the owned forms with it.

You might write your .dpr code like so:

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;

One other point to make is that perhaps you don't need to unload the DLLs. Since this is clearly an executable, the system will unload them anyway. Why do you need to do so?

1
Disillusioned On

Another option is don't let your forms implicitly reference global config.
Make the dependency explicit by giving each form its own reference to an IConfig interface.
When the RefCount of the referenced instance drops to zero (after all forms that use it have been destroyed) it can self-destruct.

Making your form's (and other object's) dependency on configuration explicit will provide other benefits.

  • It will be much easier to test.
  • Forms that don't need IConfig won't have the dependency, and won't care eaither way.
  • Therefore these forms will be easily (and obviously) moveable into other applications with slightly different framework.