Da ich mich mit dem Thema Runtime-Packages gerade beschäftigt habe und dabei einige Fragen auftauchten, dessen Antworten ich mir erst zusammensuchen musste, möchte ich mich an einem HowTo versuchen, damit andere diese Antworten gesammelt finden können.
Sinnvolle Vorkenntnisse: DLLs
Bestandteile eines Package-Projekts- *.dpk: "Delphi Package" - Hauptquelltextdatei des Packages. Sie beginnt mit dem Schlüsselwort "package". Es gibt 2 Hauptabschnitte: "requires" und "contains". Requires enthält weitere Packages, die statisch in das Package eingebunden werden. Contains enthält die im Package enthaltenen Units.
- *.dcp: "Delphi Compiled Package"
- *.bpl: "Borland Package Library" - fertiges Compilat
Arten der Einbindung von Packages
Packages sind spezielle DLLs. Man kann sie auf 2 verschiedene Weisen benutzen: statisch oder dynamisch gelinkt.
Statisches Linken heißt, dass beim Laden des Moduls das verwendete
Package mit geladen wird, bevor die Codeausführung beginnt. Wenn beispielsweise ein Programm (exe) ein
Package statisch einbindet, dann muss das
Package bei Programmstart verfügbar sein, damit es mit der exe geladen werden kann. Man kann programmtechnisch selbst nicht auf ein eventuelles Nichtvorhandensein der *.bpl-Datei reagieren - das Laden der exe bricht in diesem Fall mit einer Fehlermeldung ab.
Wird ein
Package dagegen dynamisch eingebunden, dann startet das Modul ohne Verwendung des Packages. Erst im Laufe des Programmes lädt der selbstprogrammierte Programmcode das
Package - vergleichbar mit dem dynamischen Laden einer
DLL. Was bei DLLs mit dem Aufruf von LoadLibrary und UnloadLibrary geschieht, wird bei Packages mit LoadPackage bzw. UnloadPackage erledigt.
Statisch eingebundene Runtime-Packages
Wird ein
Package statisch eingebunden, dann können die Units verwendet werden als seien sie Teil des Programms, direkt im Modul enthalten. In Packages werden statisch geladene Packages im requires-Abschnitt aufgeführt. Üblicherweise stehen hier
rtl, ggf.
vcl und weitere Packages je nach Inhalt des Packages. Um ein
Package in einer exe statisch einzubinden, muss in den Projektoptionen auf der Seite "Packages" das Häkchen bei "Laufzeit-Packages verwenden" gesetzt werden. Alle im Programm verwendeten Units werden nun nicht mehr in die exe eingebaut (bzw. der daraus erzeugte Binärcode), falls diese
Unit in einem der Packages enthalten ist, das unter dem Häkchen "Laufzeit-Packages verwenden" aufgeführt ist. Wird z.B. die
Unit Forms verwendet und das
Package vcl wird nicht als statisches Runtime-
Package eingebunden, dann wird der verwendete Quellcode aus der
Unit Forms direkt in die exe hineincompiliert. Es gibt also keinen Zwang alle voreingetragenen/verfügbaren Packages dort aufzuführen. Will man z.B. die
Unit Forms in einem
Package verwenden, ohne das
Package statisch einzubinden (in den requires-Abschnitt aufzunehmen), dann muss man die
Unit Forms und alle Units die sie verwendet (und alle, die diese Units verwenden etc. - alle implizit importierten Units) in den contains-Abschnitt aufnehmen. Dann werden all diese Units mit in das
Package-Compilat (die *.bpl) reincompiliert. Allerdings führt dieses Vorgehen bei mir zu mehreren Fehlermeldungen W1025 Sprach-Feature wird nicht unterstützt...
Wird beispielsweise in der exe das eigene
Package MyPackage als Runtime-
Package statisch eingebunden und MyPackage bindet
vcl als Runtimepackage statisch ein, dann wird
vcl automatisch auch in der exe benutzt und z.B. die
Unit Forms nicht noch einmal extra als Binärcode in der exe hinterlegt. Ein explizites Einbinden von
vcl als Runtimepackage in der exe führt in diesem Fall zu keiner Änderung von Dateigrößen - weder der exe noch der MyPackage.bpl - eventuell beeinflusst es allerdings die Ausführungsreihenfolge der
Unit-Initialisierung und -Finalisierung (ungetestet).
Dynamisch eingebundene Runtime-Packages
Bindet ein Modul Runtimepackages dynamisch ein, dann werden die Packages erst vom enthaltenen selbst programmierten Programmcode geladen. Bei entsprechender Programmierung ist das Modul also auch ohne das
Package lauffähig - im Gegensatz zu statisch eingebundenen Runtime-Packages. Allerdings ist das Handling aufwendiger. Die im
Package enthaltenen Units dürfen nirgends im Modul referenziert werden. Die Units des Packages dürfen also nicht in den uses-Abschnitten des Moduls auftauchen, wodurch auch kein im
Package definierter Bezeichner verfügbar ist. Die Verwendung findet wie folgt statt:
Quellcodebeispiel im Modul, welches das
Package verwendet:
Delphi-Quellcode:
type
TInitModule:
procedure;
// muss mit dem Typ im Package übereinstimmen
var
HPack: HModule;
InitModule: TInitModule;
begin
try
HPack := LoadPackage('
MyPackage.bpl');
if HPack > 0
then
try
@InitModule := GetProcAddress(HPack, '
InitModule');
if Assigned(InitModule)
then
InitModule
else
ShowMessage('
Einsprungadresse für Prozedur InitModule nicht gefunden');
finally
UnloadPackage(HPack);
end
else
ShowMessage('
Das Laden des Packages "MyPackage.bpl" war nicht erfolgreich');
except
on E: EPackageError
do
ShowMessage('
Fehler beim Laden des Packages "MyPackage.bpl":' +
sLineBreak + E.
Message);
end;
end;
Beispiel für eine Main-
Unit im verwendeten
Package:
Delphi-Quellcode:
unit MyPackageMain;
interface
procedure InitModule;
exports // gleiche Technik wie bei DLLs
InitModule;
implementation
uses
Dialogs;
procedure InitModule;
begin
ShowMessage('
Message aus dem Package');
end;
end.
Interaktion mit dynamisch geladenen Runtimepackages
Um auf in dynamisch geladenen Packages definierte Klassen zugreifen zu können, kann man zum Beispiel mit Klassenreferenzen,
RTTI und registrierten Klassen (RegisterClass) arbeiten. Ich bevorzuge einen anderen Weg: Ich habe mir ein
Package erstellt, welches diverse grundlegende Klassen definiert und globale Variablen hält. Dieses
Package wird sowohl im Basismodul als auch im dynamisch eingebundenen Runtimepackage (MyPackage) als statisches Runtimepackage eingebunden. Nennen wir es BasePackage. Somit stehen sowohl im Basismodul als auch in MyPackage alle Typinformationen zur Verfügung. Selbst die globalen Variablen sind die selben, da BasePackage nur einmal "instanziert" wird (oder wie man das bei Units auch immer nennt) - die Speicheradresse einer getesteten in BasePackage definierten globalen Variable, sowie natürlich auch deren Inhalt, war aus Sicht von Basismodul und MyPackage gleich. Auch die Initialization- und Finalization-Abschnitte in BasePackage wurden über die gesamte Programmlaufzeit korrekterweise nur einmal ausgeführt.
Projektorganisation und Pfadeinstellungen
Ich habe mein Programm mit allen eigenen Packages in einer Projektgruppe zusammengefasst. Achtung: Mit dem Befehl "Alle Projekte compilieren" werden die Projekte in der Reihenfolge compiliert, in der sie in der Projektgruppe angeordnet sind. Denkt an Abhängigkeiten zwischen den Projekten, beim Compilieren eines Moduls müssen alle statisch eingebundenen Packages bereits fertig compiliert vorliegen. Damit diese gefunden werden können, muss die *.dcp des statisch eingebundenen Packages im Suchpfad des Moduls verfügbar sein.
Ich verwende folgende Einstellungen: Jedes Projekt hat sein eigenes lib-Verzeichnis, in dem compilierte Units (*.dcu) landen. Alle Projekte haben ein gemeinsames Ausgabeverzeichnis für *.dcp, welches auch in alles Projekten im Suchpfad steht. Auch das Ausgabeverzeichnis für die exe und die Packages (*.bpl) muss für alle Projekte gleich sein. Wenn das Ausgabeverzeichnis außerdem die verwendeten Runtime-Packages von Delphi und Drittanbietern enthält, dann enthält es genau die Dateien, die auch der Anwender benötigt.
Es ist vor einer Auslieferung an den Anwender unbedingt empfehlenswert, die Zusammenstellung von Dateien auf einem System ohne Delphi zu testen, damit man keine *.bpl vergisst.
Visual Form Inheritance (VFI) und Packages
Wenn man einem Programm (also einem exe-Projekt) eine bestehende
Unit hinzufügt, zu der eine *.dfm-Datei existiert, dann erkennt Delphi das automatisch. Fügt man solch eine
Unit einem
Package hinzu, dann erkennt zumindest mein Delphi XE nicht, dass ein *.dfm existiert (es wird das reine
Unit-Symbol für die Datei verwendet, nicht das Form-Symbol). Das hat zur Folge, dass eine visuell abgeleitete Form nicht geöffnet werden kann, weil Delphi die Basisform nicht findet, falls diese nicht zufällig gerade angezeigt wird (geöffnet ist). Zur Abhilfe muss man von Hand den Kommentar im contains-Abschnitt in der *.dpk-Datei einfügen (und ggf. das Projekt neu öffnen):
Delphi-Quellcode:
MainView in 'MainView.pas', // TMainView ohne *.dfm - Delphi macht Probleme!
MainView in 'MainView.pas' {MainView}, // TMainView mit *.dfm - von Hand eingefügt
Namen der von Delphi mitgelieferten Packages
Wenn das
Package vcl statisch eingebunden wird, dann gehört dazu die Datei vclxxx.bpl - xxx steht hier für die Delphi-Versionsnummer, beispielsweise 150 für Delphi XE. Selbsterstellte Packages werden natürlich ohne Delphi-Versionsnummer gesucht und gefunden.
Mitzuliefernde Dateien- Die exe
- Alle statisch in die exe eingebundenen Laufzeitpackages (*.bpl), incl. der in diesen Packages statisch eingebunden Packages
- Dynamisch eingebundene Packages, sofern diese verwendet werden können sollen, incl. der in diesen Packages statisch eingebunden Packages
Achtung: Auf dem Rechner, auf dem Delphi installiert ist, sind alle installierten Runtimepackage in einem Systemverzeichnis vorhanden, so dass die exe auch ohne (in delphi installierte) Packages lauffähig ist. Auf anderen Rechnern müssen die Packages (*.bpl) entweder im Programmverzeichnis oder in einem Systemverzeichnis (welches in der Systemvariable PATH enthalten ist) zur Verfügung gestellt werden.
Links
Ich freue mich über Feedback und werde diese HowTo ggf. später noch anpassen, so lange es die Editfunktion zulässt
"Seit er seinen neuen Computer hat, löst er alle seine Probleme, die er vorher nicht hatte."