Thema: Delphi HowTo: Runtime-Packages

Einzelnen Beitrag anzeigen

RSE

Registriert seit: 26. Mär 2010
254 Beiträge
 
Delphi XE Enterprise
 
#1

HowTo: Runtime-Packages

  Alt 23. Nov 2012, 15:42
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."
  Mit Zitat antworten Zitat