Unit Initialization and Finalization for C++ New Toolchain
Go Up to Packages for C++ New Toolchain
Unit initialization and finalization dependencies can cause some issues. Since they can be a recurrent problem, RAD Studio put significant effort into improving how packages function and ensuring consistent behavior when statically and dynamically linking packages. This article attempts to explain both.
Packages are always available in two versions, the second one being as a standard static library. Statically linking a package links to that standard static library, and means the package contents end up in the binary you are linking. No files need to be redistributed for the package.
In Delphi, units have interdependencies. This means that a unit uses another unit and the types from that unit. Unit initialization and finalization dependencies can occur when a unit is initialized, in other words, when using one of the following options:
- Initialization and finalization keywords.
- Class constructors or destructors.
For instance, when initializing an object that depends on another object instance or allocation residing in a different unit. This behavior leads to units being initialized based on graph mapping dependencies. There is not necessarily a correct solution to a dependency graph, which can be circular. If so, you are likely to encounter bugs.
For example, initialization issues can often cause AVs on startup or when loading a package, mainly when relying on a unit that has not yet been initialized. RAD Studio aims to prevent or reduce these issues.
Keep in mind that only the used units are initialized and finalized when both dynamically and statically linking. Unlike earlier toolchains (Clang 5 and earlier, and classic), circular unit references are handled, and weakly packaged units are correctly handled.
How are packages dynamically initialized?
When a package is dynamically loaded, its units are recursively initialized with a counter. This ensures that when a second package using the same units is loaded, it will not be initialized twice and will only be finalized after the last package has been unloaded.
Packages can be dynamically initialized in two ways:
- Via the package import library, the
.BPI
file. This is the typical system and is how auto-linking works.
This method uses the record initialization system and consequently initializes only the units used. This may lead to a difference in the order units are initialized, if a package is dependent on two locations in the same app.
Let's explain it with an example, suppose the following scenario:App.exe
depends onA.bpl
andB.bpl
B.bpl
depends onA.bpl
.
When
App.exe
is loaded, the units it uses fromA.bpl
as any additional units on which the units themselves depend are initialized. This means that the entire unit dependency graph is initialized.
However, the units in
A.bpl
on whichB.bpl
depends are not initialized. WhenApp.exe
loadsB.bpl
, its dependencies are initialized fromA.bpl
. If any of those include units inA.bpl
that have already been initialized, those units will not be initialized twice.
This means that while each graph or set of units is initialized correctly, it is plausible that the loading of
A.bpl
byApp.exe
beforeB.bpl
causes a different overall order of initialization of A’s units than ifApp.exe
had been dependent only onB.bpl
or had loaded in the orderB.bpl
(->A.bpl
) thenA.bpl
. This should have zero impact on stability because all utilized units should be initialized in the correct order as specified in the initialization records. - Via SysUtils::LoadPackage. With this method packages are fully dynamically loaded.
All dependent package units are initialized recursively, whether they are used or not. This is because it cannot be known which ones the loading module uses from the package.
When a package is loaded its dependencies are loaded too. This means that if you load a packagePackage1.bpl
that depends onrtl.bpl
by dynamically linking to it or by usingLoadPackage
, neither you norPackage1.bpl
need to callLoadPackage(“rtlXXX.bpl”)
. That is because part of loadingPackage1.bpl
loads and initializes all the dependent package units thatPackage1.bpl
uses, which includes handling dependencies on units in other packages, such asrtl.bpl
.
An application may mix the two methods of dynamically loading a package, but it should not mix static and dynamic linking.
Unit initialization and finalization are naturally more complex as there are more levels of dependencies there are within a package, and between packages. Good testing will test complex dependencies.
When statically linking, only the used units are initialized. The linker removes unused objects, meaning units not referred to or used will not exist in the final binary.