If you’ve worked on a large Delphi project long enough, you’ve probably been bitten by it: a mysterious access violation at startup, a nil reference where an object should already exist, or an intermittent crash that only appears when you reorder your uses clause. Welcome to the world of Delphi unit initialization order — one of the most subtle and persistent sources of bugs in non-trivial applications.
In this post, I’ll talk about why this problem exists, why it gets worse as projects grow, and introduce a dependency resolution framework I’ve built for kbmMW that replaces hope-based initialization ordering with a proper directed-acyclic-graph solver. I’ll walk through the full design, from the problem it solves down to the topological sort and cycle detection at its core.
Contents
The Problem: Who Goes First?
Every Delphi unit can have an initialization section that runs automatically when your application starts. The compiler guarantees that if unit A uses unit B, then B’s initialization runs before A’s. Sounds reasonable, right?
The trouble begins when your dependency graph isn’t a simple tree. Consider a framework like kbmMW with hundreds of units, many of which reference each other through complex uses chains. The actual order in which initialization sections fire depends on the full transitive closure of your uses clauses — and crucially, on which order units appear in those clauses. Move a unit name from position 3 to position 7 in a uses list and you might change the global initialization sequence in ways that ripple across your entire application.
Here’s what this looks like in practice. Suppose kbmMWGlobal needs the RTTI system, the exception framework, a hash function unit, and a lock-free primitives unit to all be ready before it initializes. In standard Delphi, you’d rely on the compiler to figure this out from the uses graph. But the compiler only guarantees direct uses ordering. It doesn’t understand that your RunInitialization procedure semantically depends on code from units that might be three uses hops away. If some intermediate unit changes its own uses list, your carefully tested startup sequence can silently break.
This problem has a few nasty properties:
- It’s invisible. There’s no compiler warning. Things just stop working.
- It’s fragile. Adding a unit to a
usesclause in an unrelated file can change initialization order elsewhere. - It’s hard to reproduce. The order can differ between Debug and Release builds, or between Delphi versions.
- It gets worse over time. The more units in your project, the more edges in the implicit dependency graph, and the harder it becomes to reason about.
The Usual Workarounds (and Why They Fall Short)
Most Delphi developers eventually develop their own coping strategies. You might guard your initialization code with if FInstance = nil then checks. You might introduce artificial uses clauses purely to force ordering. You might centralize everything in a single “bootstrap” unit. Some teams even resort to manual initialization functions called from the .dpr file, abandoning the initialization mechanism entirely.
These all work to varying degrees, but they share a common weakness: the dependency relationships live in the developer’s head (or scattered across comments), not in the code in an enforceable, verifiable way. The day someone new joins the team and innocently refactors a uses clause, the house of cards can collapse.
A Better Way: Explicit Dependency Declarations
The approach I’ve taken in kbmMW is to make dependencies explicit and let a resolver figure out the correct order at runtime. Instead of relying on compiler-inferred uses ordering, each unit declares what it depends on and provides its own initialization and finalization procedures. A dependency graph is built at startup, topologically sorted, and each unit’s initialization is called in guaranteed-correct order.
Here’s what it looks like from the consuming unit’s perspective. This is from kbmMWGlobal.pas:
procedure RunInitialization;
begin
kbmMWTime32StartTime := TkbmMWTimeNS.NowUTC;
kbmMWDebugLevel := mwdlNone;
kbmMWDebugCS := TkbmMWLock.Create;
TkbmMWTiming.CalculateTiming;
kbmMWOSVersion := kbmMWGetOSVersion;
// ... more setup
end;
procedure RunFinalization;
begin
kbmMWMarshalledVariantType.Free;
kbmMWDebugCS.Free;
kbmMWDebugCS := nil;
end;
initialization
kbmMWDependsOn('kbmMWGlobal', @RunInitialization, @RunFinalization, [
'kbmMWExceptions',
'kbmMWHashFNV1A',
'kbmMWLockFree',
'kbmMWRTTI'
]);
kbmMWInitialize('kbmMWGlobal');
finalization
kbmMWFinalize('kbmMWGlobal');
Read that initialization block. It says: “I am kbmMWGlobal. I depend on these four units. Here are my init and finalize procedures. Now please initialize me.” That’s it. The framework handles the rest — it figures out the correct order, initializes dependencies first, and will yell at you if you’ve created a circular dependency.
The finalization section mirrors this: kbmMWFinalize tears things down in the reverse dependency order, ensuring nothing is freed while something else still needs it.
Inside the Framework: How It Works
The dependency framework lives in kbmMWDependency.pas. Let’s look at its key components.
The Dependency Registry
At the heart of everything is a global dictionary that maps unit names to dependency descriptors:
var
kbmMWAllDependencies: TkbmMWDependencies = nil;
TkbmMWDependencies is simply a TDictionary<string, IkbmMWDependency>. Each IkbmMWDependency knows its unit name, its init/finalize procedures, what it depends on, and what depends on it. That last part — the reverse edges — is crucial for finalization ordering, which I’ll come back to.
When you call kbmMWDependsOn('kbmMWGlobal', @RunInit, @RunFinalize, ['kbmMWRTTI', ...]), the framework:
- Creates (or finds) a dependency node for
kbmMWGlobal. - Stores references to
RunInitializationandRunFinalization. - For each dependency name, creates (or finds) its node and wires up both directions:
kbmMWGlobaldepends-onkbmMWRTTI, andkbmMWRTTIis depended-on-bykbmMWGlobal.
This bidirectional linking is what makes finalization work correctly — but we’re getting ahead of ourselves.
Registration vs. Initialization: A Two-Phase Protocol
Notice that the initialization section makes two calls:
initialization
kbmMWDependsOn('kbmMWGlobal', @RunInitialization, @RunFinalization, [...]);
kbmMWInitialize('kbmMWGlobal');
The first call registers the dependency. The second call requests initialization. These are deliberately separate because Delphi’s initialization sections can fire in any order. When kbmMWGlobal‘s initialization section runs, the units it depends on might not have registered yet.
The framework handles this gracefully. When kbmMWInitialize is called, the resolver checks whether all dependencies are defined. If they are, it resolves and initializes. If not, it defers — the request is simply noted, and a sweep runs after each kbmMWInitialize call to pick up anything whose dependencies have since become available.
This is the kbmMWTriggerInitializations mechanism: after every explicit kbmMWInitialize call, it iterates over all known units and tries to initialize any that are defined but not yet initialized. This means the system is self-healing with respect to registration order. No matter which unit’s initialization section fires first, eventually all dependencies will be satisfied and everything will be initialized in the correct order.
The Topological Sort
When kbmMWInitialize('kbmMWGlobal') runs and all dependencies are available, it creates a TkbmMWDependencyInitializer and calls ResolveFor. This is where the real graph algorithm lives.
The resolver first builds a subgraph — it walks the dependency tree starting from the requested unit and collects only the nodes that are reachable. There’s no reason to sort the entire universe of units when you only need to initialize one subtree.
Then it performs Kahn’s algorithm for topological sorting:
// Count incoming edges for each node in the subgraph
for u in FSubGraph.Values do
inDegrees.AddOrSetValue(u.&Unit, u.DependsOnList.Count);
// Seed the queue with nodes that have no dependencies
for u in FSubGraph.Values do
if inDegrees[u.&Unit] = 0 then
q.Enqueue(u);
// Process: dequeue, add to result, decrement neighbors
while q.Count > 0 do
begin
u := q.Dequeue;
AInitializationOrder.Add(u);
for p in u.DependedOnList do
begin
v := IkbmMWDependency(p);
if inDegrees.ContainsKey(v.&Unit) then
begin
inDegrees.AddOrSetValue(v.&Unit, inDegrees[v.&Unit] - 1);
if inDegrees[v.&Unit] = 0 then
q.Enqueue(v);
end;
end;
end;
If you haven’t seen Kahn’s algorithm before, the idea is elegant: start with nodes that have zero dependencies (no incoming edges). Process them, then “remove” them from the graph by decrementing the in-degree of everything that depends on them. When a node’s in-degree hits zero, it’s ready. Repeat until everything is processed.
If the output list contains fewer nodes than the subgraph, something couldn’t be resolved — which means there’s a cycle.
Cycle Detection: The Three-Color DFS
When the topological sort detects an unresolvable situation, the framework doesn’t just throw its hands up. It runs a dedicated cycle finder to tell you exactly which units form the cycle.
The cycle finder uses the classic white-gray-black DFS approach:
- White nodes are unvisited.
- Gray nodes are currently being visited (in the recursion stack).
- Black nodes are fully processed.
If during DFS you encounter a gray node, you’ve found a back-edge — a cycle. The finder tracks the current path and extracts exactly the cycle portion:
// Move from white (unvisited) to gray (visiting)
FWhiteSet.Remove(AUnit.&Unit);
FGraySet.Add(AUnit.&Unit, AUnit);
APath.Add(AUnit);
for p in AUnit.DependsOnList do
begin
d := IkbmMWDependency(p);
// Gray neighbor = cycle!
if FGraySet.ContainsKey(d.&Unit) then
begin
Result := TList<IkbmMWDependency>.Create;
j := APath.IndexOf(d);
for i := j to APath.Count - 1 do
Result.Add(APath[i]);
exit;
end;
// White neighbor = recurse deeper
if FWhiteSet.ContainsKey(d.&Unit) then
begin
sub := Visit(d, APath);
if sub <> nil then
begin
Result := sub;
exit;
end;
end;
end;
// Done visiting, move to black
FGraySet.Remove(AUnit.&Unit);
FBlackSet.Add(AUnit.&Unit, AUnit);
APath.Delete(APath.Count - 1);
The output is a human-readable path like kbmMWFoo -> kbmMWBar -> kbmMWBaz -> kbmMWFoo, making it immediately clear where the problem is. In debug mode, this even pops up a message box on Windows so you can’t miss it. In production, it calls Halt — a circular dependency is a fatal configuration error, not something to limp along with.
Finalization: The Mirror Image
Finalization is the mirror of initialization. Where initialization follows the DependsOn edges (initialize what I need first), finalization follows the DependedOn edges (finalize what needs me first).
The TkbmMWDependencyFinalizer builds its subgraph by walking DependedOnList instead of DependsOnList, and its Kahn’s algorithm seeds with nodes that have zero DependedOnList count (nothing depends on them, so they’re safe to finalize first). The result is that teardown happens in precisely the reverse order of setup — no use-after-free, no dangling references.
This is why the bidirectional linking during registration matters. Without the reverse edges, you’d need to invert the entire initialization order to get the finalization order. With them, finalization is solved independently, which is more robust and handles cases where the finalization graph might differ from the initialization graph.
Deferred Initialization and the Trigger Sweep
The deferred initialization mechanism deserves special mention because it solves a real chicken-and-egg problem. When Delphi fires initialization sections, the order is compiler-determined. Unit A might register and request initialization before Unit B has even registered. The framework returns gracefully with a “not ready yet” status.
But who retries? After every kbmMWInitialize call, the framework runs kbmMWTriggerInitializations:
procedure kbmMWTriggerInitializations;
var
allUnitNames: TArray<string>;
unitName: string;
d: IkbmMWDependency;
begin
if kbmMWAllDependencies = nil then
exit;
allUnitNames := kbmMWAllDependencies.Keys.ToArray;
for unitName in allUnitNames do
begin
if kbmMWAllDependencies.TryGetValue(unitName, d) then
if d.IsDefined and not d.IsInitialized then
kbmMWInitialize(d.&Unit, true);
end;
end;
Each time a new unit registers and initializes, the sweep picks up any previously deferred units whose dependencies are now satisfied. The AIsTriggeredCall parameter prevents infinite recursion — triggered calls don’t trigger further sweeps.
This means the system converges naturally. As more and more units register through their initialization sections, each sweep resolves a few more, until everything is initialized. No explicit ordering. No manual bootstrapping. The graph takes care of it.
Validation and Debugging
The framework also provides kbmMWValidateDependencies, which you can call after startup to verify that every registered dependency was both fully defined and successfully initialized. If anything slipped through — perhaps a unit was referenced as a dependency but never actually included in the project — you’ll get a clear report.
There’s also a debug mode ({$DEFINE KBMMW_DEBUG_DEPENDENCY}) that logs every registration, initialization, and finalization call through OutputDebugString on Windows or Log.d on mobile. When you’re chasing a startup issue, this trace is invaluable — it shows you the exact order everything happened and where things went wrong.
Why This Matters Beyond Frameworks
While I built this for kbmMW, the pattern applies to any large Delphi project. If you have more than a handful of units with initialization sections that depend on each other, you’re sitting on a potential ordering bug. The risk grows superlinearly with project size — each new unit adds edges to the implicit dependency graph, and the chance of an accidental reordering increases.
The explicit dependency declaration approach has several advantages over the implicit compiler-determined approach:
- Dependencies are documented in code. When you read a unit’s
initializationsection, you see exactly what it needs. No guessing, no tracinguseschains. - Cycles are detected immediately. Instead of a mysterious access violation three stack frames deep, you get a clear error message naming the exact units involved.
- Order is deterministic. It doesn’t matter how the compiler traverses the
usesgraph. The topological sort always produces a valid order. - Finalization is automatic. No more wondering if you’re freeing something too early. The reverse-dependency sort handles it.
- It’s testable. You can call
kbmMWValidateDependenciesin your test suite to catch configuration errors before they reach production.
The trade-off is a small amount of ceremony — each unit needs to register its dependencies explicitly rather than relying on the implicit uses ordering. In practice, this is a few lines per unit, and the clarity it provides is well worth the effort.
Wrapping Up
Delphi’s unit initialization mechanism is powerful and convenient for small projects. But as your codebase grows, the implicit ordering it provides becomes a source of subtle, hard-to-diagnose bugs. By replacing hope with a proper dependency graph, topological sort, and cycle detection, you get deterministic startup behavior, clear error messages, and correct finalization ordering — all without giving up the convenience of per-unit initialization code.
The full implementation is in kbmMWDependency.pas. It’s under a thousand lines, has no external dependencies beyond Generics.Collections, and solves a problem that has quietly caused headaches in the Delphi community for decades.
If you’ve ever added a comment saying // IMPORTANT: This unit must appear before X in the uses clause, it might be time to try a different approach.
![]()






