Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why code in any unit finalization section of a package is not executed at shut down?

I have an application that uses statically-linked runtime packages as well as designtime packages that use them. For some reason the code in any unit finalization section is not being run at runtime (I can't tell when this started happening).

finalization
  ShowMessage('Goodbye');
end.

Shutting Delphi down shows the message, but not when my application shuts down. It gets weirder in that if I put a breakpoint on ShowMessage, it breaks there but does not execute the line. If there are multiple lines in the finalization, the debugger stops on the first line, does not execute it and then jumps to the end.

procedure ProcOne;
begin
  SomeObject.Free; // Debugger does not enter or stop here
  SomeObject := nil;
end;

finalization
  ProcOne; // Debugger stops here, doesn't execute, jumps to "end."
  ProcTwo; // Every line has a blue dot
  ShowMessage('Bye');
end.

The call stack on ProcOne breakpoint shows @Halt0 => FinalizeUnits => MyPackage.MyUnit.Finalization.

If I include the unit in an application that doesn't use packages, everything executes properly.

Does anyone have an idea what could be causing this?

EDIT:

Thanks to Allen Bauer's comment pointing in the right direction, I have managed to isolate the problem. It seems the problem arises if an application is built with a runtime package, then dynamically loads another package that also references that package and unit.

I have created a test project that demonstrates the problem: TestFinalization

Does anyone know the reason for this and/or a workaround? You normally might not notice that your finalization is not being run until you notice that external resources are not being cleaned up.

like image 244
avenmore Avatar asked Sep 27 '11 09:09

avenmore


1 Answers

Make sure you're calling UnloadPackage for each dynamically loaded package before shutdown. If you're simply calling UnloadLibrary (or simply relying on the OS to unload them), then the finalizations for that the units in that package and all the units from other packages aren't being called. Initializations and finalizations are done using a reference counting system because in the face of dyanmically loaded packages, there is no way to know what units will be initialized and when. Only when you've balanced the finalization calls with the initialization calls will the last finalization call actually execute the code block in the finalization section. Likewise only the first call to the initialization section will actually execute the code block.

Initializations/finalizations are done using a compiler-generated table for a given module. When you build an exe or dll linked with packages, this table contains references to all the units that are actually used, even those from the linked packages. Note that only the units actually referenced are actually initialized. IOW, if you have 100 units in PackageA and the exe only references one of them, then only that unit and any units it uses will be initialized.

For dynamically loaded packages, there is really no way to know what units will actually be used, so the compiler generates the init/finit table as if every unit were initialized. This table is not processed upon loading of the package during the call to LoadLibrary, but rather is handled by calling a special export called Initialize(). The LoadPackage function ensures that this function is called. This table only ensures that all the units in the loading package are initialized. Only the units actually touched in any other package are initialized, similar to the exe/dll case I mentioned above. UnloadPackge does the reverse, and calls special export Finalize() before calling UnloadLibrary().

Finally, if you've made changes to uses lists of any packaged units and only rebuild the package, you can run into confusing cases where initializations/finalizations may not get called even though your units within a given package properly "use" each other. This is because the init/finit is controlled by the loading module and not from within itself. Only in the case where a package is explicitly loaded using LoadPackage will every unit in that package (and that package only) be initialized/finalized.

like image 50
Allen Bauer Avatar answered Sep 30 '22 05:09

Allen Bauer