Delphi and finalization in a unit
Asked Answered
A

6

5

I have two units unitA and unitB. Class TFoo is declared in unitB.

Is it allways safe to call B.Free in finalization of unitA?

How does it depend on in which order unitA and unitB are in dpr?

Can I be sure that unitB exists when unitA finalization is executed?

unit unitB;
interface
type
 TFoo = class
   // code...
  end;
 // code....
end;

unit unitA;
// code..
implementation
uses
 unitB;
var
 A: TStringList;
 B: UnitB.TFoo;

initialization
 A:= TStringList.Create;
 B:= UnitB.TFoo.Create;
finalization
 A.Free;
 B.Free;  // Is it safe to call?
end.
Apollinaire answered 20/2, 2010 at 6:47 Comment(0)
S
17

Yes, you should be fine since B is created in Unit A. The rule is that the Initialization sections are called based on the order they are in the DPR, unless one of the units references another unit. In that case, the referenced unit is initialized first. Finalization is in the reverse order.

In your case Unit B does not have an initialization section, so it is a moot point. It will however use the TFoo definition in Unit B when the Unit A initialization section is executed.

Another word of warning about Initialization and Finalization sections - they happen outside of the global exception handler. Any exception that occurs there just terminates the application. So tracking down and debugging those exceptions can be a pain in large programs. You might consider using your own exception logging in there, just to be sure.

Sidewheel answered 20/2, 2010 at 7:26 Comment(2)
See my comment. Delhi doesn't guarantee order. Your exception note is true.Lardon
It's worth to note that Madexcept catches those exceptions raised in finalization sections.Bodgie
L
5

NO. You can try, you can hope but there is no guarantee in order of calling initialization and finalization. See qc72245, qc56034 and many more.

UPDATE:

  1. finalization section is executed in reverse order than initialization. Your example is safe, you don't have dependency on calling initialization sections between units
  2. Delphi can mix calling units (point 1 is still valid, both initialization and finalization sections are swapped)

Example:

unitA // no dependency on unitB
var SomeController;
initialization
  SomeController := TSomeController.Create;
finalization
  SomeController.Free;

unitB
uses
  unitA;
initialization
  SomeController.AddComponent(UnitBClass);
finalization
  SomeController.RemoveComponent(UnitBClass);

Common (correct) order (99.99%) of calling:

  1. unitA.initialization
  2. unitB.initialization
  3. run...
  4. unitB.finalization
  5. unitA.finalization

But sometimes can Delphi compile file wrong:

  1. unitB.initialization - AV here
  2. unitA.initialization
  3. run...
  4. unitA.finalization
  5. unitB.finalization - and here too

Little offtopic story:

We have quite big project, with Type1 in Unit1, Type2 = class(Type1) in Unit2. Files are ordered in project.dpr and after years and adding Unit200 (no dependency with unit1/2) Delphi starts compiling project with Unit2.Initialization before Unit1.Initialization. Only safe solution is calling your own Init functions from initialization section.

Lardon answered 20/2, 2010 at 10:42 Comment(5)
The original question is not about the initialization/finalization order.Fording
Even if unit B is finalized first, that doesn't cause the unit to stop existing. Code that uses things defined in unit B will continue to work.Fulgurant
But it depends. finalization parts are executed in reverse order to initialization, but Delphi can swap units and orderLardon
@Fording - But the information is still too good to be deleted. Good job DiGi.Horthy
I like your unit names :)Horthy
A
3

As far as I understand it what you've got should be perfectly valid. A little awkward but valid.

But a better way might be to declare a variable in Unit B and have B initialize/finalize it. Since initializations happen before any other code is called it will be initialize before it is made available to Unit A as long as it is declared in the uses clause of Unit A.

One other step you might want to consider is taking the unit variable of B one step further and have it as a function call for on demand loading, but that might also be dependant on your usage.

for example

unit unitB;
interface
type
 TFoo = class
   // code...
  end;
 // code....
 function UnitVarB:TFoo;

implementation

var
  gUnitVarB : TFoo;  

function UnitVarB:TFoo 
begin
  if not assigned(gUnitVarB) then
    gUnitVarB := TFoo.Create;

  result := gUnitVarB;
end;

finalization
  if assigned(gUnitVarB) then
    gUnitVarB.free;  //or FreeAndNil(gUnitVarB);

end;

unit unitA;

// code..
implementation

uses
 unitB;

var
 A: TStringList;

//code...
  ...UnitVarB....
//code...

initialization
 A:= TStringList.Create;
finalization
 A.Free;
end.

I seem to remember somewhere that unit initializations could be expensive in that if a unit that you no longer directly reference is still in your uses clause during a compile, the smart linker will not remove it because of the initialization section. While this may not sound that bad if every unit had an initialization section then most Delphi programs would be MUCH bigger than they already are.

I'm not saying don't use them but my rule of thumb is to use them sparingly.

Your initial code example breaks that rule. I thought I'd mention it.

Ryan

Anterior answered 20/2, 2010 at 7:12 Comment(2)
Thanks. Function UnitVarB is clever way to avoid global variable.Apollinaire
I use CnPack's uses cleaner. It seems to skip units that have initialization.Apollinaire
O
3

In the specific situation you are showing here, you will be alright. But it wouldn't take that much refactoring before it starts going wrong.

Delphi does a pretty good job making sure units stays in memory as long as they are needed. But it can only do so if it knows a unit is needed.

My classical example on the topic is a unit containing nothing but an objectlist

unit Unit1;

interface
uses
  Contnrs;
var
  FList : TObjectList;
implementation

initialization
  FList := TObjectList.Create(True);
finalization
  FList.Free;
end.

Unit1 is only explicitly dependant on Contnrs. Delphi will only ensure the Contnrs unit (and probably also "subdependant" units, though I'm not 100% sure) is still loaded in memory. If a TForm is added in the list, the Forms unit might be already finalized when FList.free is called it will crash when it tries to free the TForm it contains. Delphi has no way to know Unit1 requires the Forms unit. In this specific case, it will depend on the order units are declared in the dpr.

Opsonin answered 21/11, 2014 at 21:25 Comment(0)
F
1

Yes, that is safe. You can simplify the compiler's job by declaring UnitB prior to UnitA in dpr file, but the compiler will resolve the references in any case.

Fording answered 20/2, 2010 at 7:21 Comment(0)
C
1

In the spirit of full disclosure, I haven't developed in Delphi since 2005. However, I developed in Delphi exclusively starting with Delphi 1 in 1996, and was certified in Delphi 5 in 2001. That being said, my use of the finalization section was rare. The only time I would use it is if I needed to set up something special in the .dpr. That typically only occurred if I was doing custom component development, AND there were some dependencies that I needed to manage using other custom components I was developing.

For typical application development, I stayed away from the initialization/finalization section and just used design patterns like singletons, facades and factories to manage the creation and management of my classes. The built-in garbage collector was good enough for 98.5% of my projects.

To answer your question, you need to set up a dependency on TFoo in your UnitA code, and, as Ryan suggested, make sure it's assigned prior to destruction. That being said, I encourage you to make sure that the use of the initialization/finalization section is necessary before you invest too much time with it.

Cyndy answered 20/2, 2010 at 7:25 Comment(3)
Delphi does not have a garbage collector.Sidewheel
The poster is most likely referring to the VCL "Owner" behaviour and/or the fact that memory for objects that are not explicitly Free'd is returned to the system when the process terminates, although of course their destructors do not run.Jari
Jim: Thanks for reminding me...apparently, the exorcism was a complete success. :o) Deltics: I remember having to use FreeAndNil often to release the memory and the pointer in my code for objects I created in code, but I wouldn't use the initialization section unless I was working with a system-managed resource, like an COM object. Even then, it was typically associated with dependent libraries or making sure resources were available when my component started.Cyndy

© 2022 - 2024 — McMap. All rights reserved.