开发者

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

I have an application that us开发者_运维问答es 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.


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.


For anyone in the same situation as me, in light of Allen Bauer's answer:

I have a number of units which use initialization/finalization to self-register. In the spirit of Raymond Chen's advice I only run deregistration when it matters:

initialization
  RegisterUnit();
finalization
//In debug mode we track memory leaks so properly deregister
//In release mode the app is shutting down; do not waste time
//freeing memory that's going to be freed anyway
{$IFDEF DEBUG}
  UnloadUnit();
{$ENDIF}

I moved a bunch of these into packages but this broke core package finalization, as the question describes.

From Allen Bauer's answer it follows that you must call UnloadPackage() for all dynamically loaded packages, or you will not get proper finalization calls in the core.

But then I cannot use that optimization anymore. I have to painfully deregister every package on finalization because once I unload the package DLL the objects it registered in the core are going to be zombies.

This feels like a wasted effort. Unloading should be quick. I'd like to leave all dynamically loaded packages hanging until everything is simply destroyed.

What you can do instead is call FinalizePackage() on all dynamically loaded packages! This evens out the reference counters while leaving the packages loaded.

If a package employs this "skip deinit" trick its objects are going to remain alive until the process destruction. (It's the job of the package to make sure anything that can be called on them does not break). If it doesn't, it's fully deinitialized and what remains is an inert DLL.

This wouldn't work with packages that you plan on unloading truly dynamically of course.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜