Unit Initialization and Finalization for C++ New Toolchain

From RAD Studio
Jump to: navigation, search

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.

Note: Keep in mind, that dynamically linking to a package uses a dynamic BPL file, which must be distributed with an application. A BPL is a dynamically loaded module, in other words, it is basically a DLL with some extras.

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.

Previously, linking dynamically and statically used different methods for initialization and finalization of units and data, which caused different behavior (and bugs) depending on how an app was linked. Now, a unified method simplifies and speeds up the resolution of issues, guaranteeing close to identical behavior for both types of links.

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:

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.

Note: We will refer to ‘initialization’ in this article, but this also applies to finalization, in reverse.

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 on A.bpl and B.bpl
    • B.bpl depends on A.bpl.

    When App.exe is loaded, the units it uses from A.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 which B.bpl depends are not initialized. When App.exe loads B.bpl, its dependencies are initialized from A.bpl. If any of those include units in A.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 by App.exe before B.bpl causes a different overall order of initialization of A’s units than if App.exe had been dependent only on B.bpl or had loaded in the order B.bpl (-> A.bpl) then A.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 package Package1.bpl that depends on rtl.bpl by dynamically linking to it or by using LoadPackage, neither you nor Package1.bpl need to call LoadPackage(“rtlXXX.bpl”). That is because part of loading Package1.bpl loads and initializes all the dependent package units that Package1.bpl uses, which includes handling dependencies on units in other packages, such as rtl.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.

See Also