Unit-Initialisierung und -Finalisierung für die neue C++-Toolkette

Aus RAD Studio

Nach oben zu Packages für die neue C++-Toolkette


Abhängigkeiten bei der Initialisierung und Finalisierung von Units können zu Problemen führen. Da dies ein wiederkehrendes Problem sein kann, wurden für RAD Studio erhebliche Anstrengungen unternommen, um die Funktionsweise von Packages zu verbessern und ein einheitliches Verhalten beim statischen und dynamischen Linken von Packages sicherzustellen. In diesem Artikel wird beides erklärt.

Hinweis: Bitte beachten Sie, dass beim dynamischen Linken mit einem Package eine dynamische BPL-Datei verwendet wird, die mit der Anwendung weitergegeben werden muss. Eine BPL ist ein dynamisch geladenes Modul, d. h., es handelt sich im Grunde um eine DLL mit einigen Extras.

Packages sind immer in zwei Versionen verfügbar, wobei die zweite als statische Standardbibliothek vorliegt. Beim statischen Linken eines Package wird es mit dieser statischen Standardbibliothek gelinkt. Das bedeutet, der Package-Inhalt wird in die Binärdatei übernommen, die Sie linken. Für das Package müssen keine Dateien weitergegeben werden.

Früher wurden für dynamisches und statisches Linken unterschiedliche Methoden für die Initialisierung und Finalisierung von Units und Daten verwendet, was je nach Art des Linkens einer Anwendung zu unterschiedlichem Verhalten (und Fehlern) führte. Jetzt vereinfacht und beschleunigt ein einheitliches Verfahren die Behebung von Problemen und gewährleistet ein nahezu identisches Verhalten für beide Arten des Linkens.

In Delphi haben Units gegenseitige Abhängigkeiten. Das bedeutet, dass eine Unit eine andere Unit und die Typen aus dieser Unit verwendet. Abhängigkeiten bei der Initialisierung und Finalisierung von Units können vorkommen, wenn eine Unit initialisiert wird. Das heißt, wenn eine der folgenden Optionen verwendet wird:

Beispielsweise bei der Initialisierung eines Objekts, das von einer anderen Objektinstanz oder -zuweisung in einer anderen Unit abhängig ist. Dieses Verhalten führt dazu, dass Units auf Grundlage von Abhängigkeiten in der Graphenzuordnung initialisiert werden. Es gibt nicht unbedingt eine richtige Lösung für einen Abhängigkeitsgraphen, der zirkulär sein kann. Wenn dem so ist, dann werden wahrscheinlich Fehler auftreten.

Initialisierungsprobleme können z. B. häufig AVs beim Start oder beim Laden eines Package verursachen, hauptsächlich wenn sie von einer Unit abhängig sind, die noch nicht initialisiert wurde. In RAD Studio wurde versucht, diese Probleme zu vermeiden oder zu verringern.

Hinweis: In diesem Artikel sprechen wir von "Initialisierung", doch dies gilt umgekehrt auch für die Finalisierung.

Beachten Sie, dass sowohl beim dynamischen als auch beim statischen Linken nur die verwendeten Units initialisiert und finalisiert werden. Im Gegensatz zu früheren Toolketten (Clang 5 und früher und klassisch) werden zirkuläre Unit-Referenzen behandelt und schwach gepackte Units werden korrekt verarbeitet.

Wie werden Packages dynamisch initialisiert?

Wenn ein Package dynamisch geladen wird, werden seine Units rekursiv mit einem Zähler initialisiert. Dadurch wird sichergestellt, dass ein zweites Package, das dieselben Units verwendet, nicht doppelt initialisiert wird und erst dann finalisiert wird, wenn das letzte Package entladen wurde.

Packages können auf zwei Arten dynamisch initialisiert werden:

  • Über die Package-Importbibliothek, die .BPI-Datei. Dies ist das übliche Verfahren, und so funktioniert das automatische Linken.
    Diese Methode verwendet das Record-Initialisierungssystem und initialisiert konsequent nur die verwendeten Units. Dies kann zu einer unterschiedlichen Reihenfolge führen, in der Units initialisiert werden, wenn ein Package von zwei Stellen in derselben Anwendung abhängig ist.
    Lassen Sie uns das anhand eines Beispiels erläutern. Nehmen wir folgendes Szenario an:
    • App.exe hängt von A.bpl und B.bpl ab.
    • B.bpl hängt von on A.bpl ab.

    Wenn App.exe geladen wird, werden die Units initialisiert, die sie aus A.bpl sowie aus allen weiteren Units verwendet, von denen die Units selbst abhängig sind. Das bedeutet, der gesamte Unit-Abhängigkeitsgraph wird initialisiert.

    Die Units in A.bpl, von denen B.bpl abhängig ist, werden jedoch nicht initialisiert. Wenn App.exe B.bpl lädt, werden ihre Abhängigkeiten aus A.bpl initialisiert. Wenn eine davon Units in A.bpl einschließt, die bereits initialisiert wurden, werden diese Units nicht zweimal initialisiert.

    Das bedeutet, dass zwar jeder Graph bzw. jede Gruppe von Units korrekt initialisiert wird, es jedoch denkbar ist, dass durch das Laden von A.bpl durch App.exe vor B.bpl eine andere Initialisierungsreihenfolge der Units von A verursacht werden kann, als wenn App.exe nur von B.bpl abhängig wäre oder in der Reihenfolge B.bpl (-> A.bpl) dann A.bpl geladen worden wäre. Dies sollte keinerlei Auswirkungen auf die Stabilität haben, da alle verwendeten Units in der richtigen Reihenfolge initialisiert werden sollten, wie in den Initialisierungs-Records angegeben.

  • Über SysUtils::LoadPackage. Mit dieser Methode werden Packages vollständig dynamisch geladen.
    Alle abhängigen Package-Units werden rekursiv initialisiert, ob sie verwendet werden oder nicht. Und zwar deshalb, weil nicht bekannt ist, welche das ladende Modul aus dem Package verwendet.
    Wenn ein Package geladen wird, werden auch seine Abhängigkeiten geladen. Das bedeutet: Wenn Sie ein Package namens Package1.bpl laden, das von rtl.bpl abhängt – sei es durch dynamisches Linken oder mithilfe von LoadPackage –, müssen weder Sie noch Package1.bpl LoadPackage(“rtlXXX.bpl”) aufrufen. Das liegt daran, dass beim Laden von Package1.bpl alle abhängigen Package-Units, die Package1.bpl verwendet, ebenfalls geladen und initialisiert werden, wozu auch die Behandlung von Abhängigkeiten zu Units in anderen Packages gehört, wie beispielsweise rtl.bpl.

In einer Anwendung können die beiden Methoden zum dynamischen Laden eines Package kombiniert werden, aber statisches und dynamisches Linken sollten nicht miteinander vermischt werden.

Unit-Initialisierung und -Finalisierung sind naturgemäß komplexer, da es mehr Abhängigkeitsebenen als in einem Package und zwischen Packages gibt. Gute Tests prüfen komplexe Abhängigkeiten.

Beim statischen Linken werden nur die verwendeten Units initialisiert. Der Linker entfernt nicht verwendete Objekte. D. h. Units, auf die nicht verwiesen wird oder die nicht verwendet werden, sind in der endgültigen Binärdatei nicht enthalten.

Siehe auch