Dynamisch ladbare Bibliotheken schreiben

Aus Appmethod Topics
Wechseln zu: Navigation, Suche

Nach oben zu Bibliotheken und Packages - Index


Hinweis: Hinsichtlich der Exportmöglichkeiten sind Bibliotheken gegenüber Packages deutlich eingeschränkter. Bibliotheken können keine Konstanten, Typen und normale Variablen exportieren. Das heißt, dass in einer Bibliothek definierte Klassentypen für ein Programm, das diese Bibliothek verwendet, nicht sichtbar sind.

Um andere Elemente als einfache Prozeduren und Funktionen zu exportieren, sind Packages die empfohlene Alternative. Bibliotheken sollten nur verwendet werden, wenn eine Interoperabilität mit anderen Programmen erforderlich ist.


In den folgenden Abschnitten werden die Elemente beschrieben, die bei der Erstellung einer dynamisch ladbaren Bibliothek zum Einsatz kommen:

  • Die exports-Klausel
  • Code für die Initialisierung der Bibliothek
  • Globale Variablen
  • Bibliotheken und Systemvariablen

Die exports-Klausel

Die Grundstruktur einer dynamisch ladbaren Bibliothek ist identisch mit der eines Programms. Der einzige Unterschied besteht darin, dass sie nicht mit program, sondern mit dem reservierten Wort library beginnt.

Andere Bibliotheken und Programme können nur auf die Routinen zugreifen, die eine Bibliothek explizit exportiert. Das folgende Beispiel zeigt eine Bibliothek mit den exportierten Funktionen Min und Max:

library MinMax;
function Min(X, Y: Integer): Integer; stdcall;
begin
  if X < Y then Min := X else Min := Y;
end;
function Max(X, Y: Integer): Integer; stdcall;
begin
  if X > Y then Max := X else Max := Y;
end;
exports
  Min,
  Max;
  begin
 end.


Sie können eine Bibliothek auch Anwendungen zur Verfügung stellen, die in anderen Sprachen geschrieben wurden. Für diesen Zweck ist es am sichersten, wenn Sie in den Deklarationen exportierter Funktionen die Direktive stdcall angeben. Die standardmäßig verwendete Object Pascal-Aufrufkonvention register wird nicht von allen anderen Sprachen unterstützt.

Bibliotheken können aus mehreren Units bestehen. In diesem Fall enthält die Quelltextdatei der Bibliothek häufig nur eine uses-Klausel, eine exports-Klausel und den Initialisierungscode. Zum Beispiel:

library Editors;
uses EdInit, EdInOut, EdFormat, EdPrint;
exports
  InitEditors,
  DoneEditors name Done,
  InsertText name Insert,
  DeleteSelection name Delete,
  FormatSelection,
  PrintSelection name Print,
    .
    .
    .
  SetErrorHandler;
 begin
   InitLibrary;
 end.

Die exports-Klauseln können in den interface- oder implementation-Abschnitt einer Unit eingefügt werden. Alle Bibliotheken mit einer solchen Unit in ihrer uses-Klausel exportieren automatisch die Routinen, die in der exports-Klausel der Unit aufgelistet sind, ohne dass eine eigene exports-Klausel benötigt wird.


Eine Routine wird exportiert, wenn sie in einer exports-Klausel wie der folgenden angegeben wird:

exports Eintritt1, ..., Eintrittn;

Eintritt steht für den Namen einer Prozedur, Funktion oder Variable, die vor der exports-Klausel deklariert werden muss. Darauf folgt eine Parameterliste (nur beim Exportieren einer überladenen Routine) und ein optionaler name-Bezeichner. Der Name der Prozedur oder Funktion kann mit dem Namen einer Unit qualifiziert werden.

(Eintrittspunkte können außerdem die Direktive resident enthalten, die der Abwärtskompatibilität dient und vom Compiler ignoriert wird.)

Der Bezeichner index (nur Win32) besteht aus der Direktive index und einer numerischen Konstante von 1 bis 2.147.483.647 (je niedriger die Konstante, desto effizienter das Programm). Ist kein index-Bezeichner angegeben, wird der Routine in der Exporttabelle automatisch eine Nummer zugewiesen.

Hinweis: Der Bezeichner index dient lediglich der Abwärtskompatibilität und sollte nach Möglichkeit nicht verwendet werden, da er in anderen Entwicklungstools zu Problemen führen kann.

Ein name-Bezeichner besteht aus der Direktive name und einer nachfolgenden String-Konstante. Verfügt ein Eintrittspunkt über keinen name-Bezeichner, wird die Routine unter ihrem ursprünglich deklarierten Namen (in derselben Schreibweise) exportiert. Verwenden Sie die name-Klausel, wenn Sie eine Routine unter einem anderen Namen exportieren wollen. Zum Beispiel:

exports
DoSomethingABC name 'DoSomething';

Wenn eine überladene Funktion oder Prozedur aus einer dynamisch ladbaren Bibliothek exportiert wird, muss die exports-Klausel die zugehörige Parameterliste enthalten. Zum Beispiel:

exports
Divide(X, Y: Integer) name 'Divide_Ints',
Divide(X, Y: Real) name 'Divide_Reals';

In Win32 dürfen überladene Routinen keine index-Bezeichner enthalten.

Die exports-Klausel kann im Deklarationsteil des Programms oder der Bibliothek bzw. im interface- oder implementation-Abschnitt einer Unit an beliebigen Stellen und beliebig oft angegeben werden. Programme enthalten nur selten eine exports-Klausel.

Code für die Initialisierung der Bibliothek

Die Anweisungen im Block einer Bibliothek bilden den Initialisierungscode der Bibliothek. Diese Anweisungen werden nur einmal beim Laden der Bibliothek ausgeführt. Mit diesen Anweisungen werden beispielsweise Fensterklassen registriert und Variablen initialisiert. Außerdem kann der Initialisierungscode einer Bibliothek mithilfe der Variablen DllProc eine Eintrittspunkt-Prozedur installieren. Die Variable DllProc ist einer Exit-Prozedur ähnlich, die im Abschnitt "Exit-Prozeduren" beschrieben wird. Die Eintrittspunkt-Prozedur wird beim Laden oder Entladen der Bibliothek ausgeführt.

Der Initialisierungscode einer Bibliothek kann einen Fehler signalisieren. Zu diesem Zweck wird der Variable ExitCode ein Wert zugewiesen, der ungleich Null ist. ExitCode ist in der Unit System deklariert und hat den Standardwert Null. Dieser Wert gibt an, dass die Initialisierung erfolgreich war. Wenn der Initialisierungscode der Bibliothek der Variable ExitCode einen anderen Wert zuweist, wird die Bibliothek aus dem Speicher entfernt, und die aufrufende Anwendung wird über den Fehler benachrichtigt. Tritt während der Ausführung des Initialisierungscodes eine nicht behandelte Exception auf, wird die aufrufende Anwendung über einen Fehler beim Laden der Bibliothek benachrichtigt.

Hier ein Beispiel für eine Bibliothek mit Initialisierungscode und einer Eintrittspunkt-Prozedur:

library Test;
var
  SaveDllProc: Pointer;
procedure LibExit(Reason: Integer);
begin
  if Reason = DLL_PROCESS_DETACH then
  begin
    .
    .   // Exit-Code der Bibliothek
    .
  end;
  SaveDllProc(Reason);	// Speichern der Eintrittspunkt-Prozedur
end;
begin
    .
    .	  // Code für die Initialisierung der Bibliothek
    . 
  SaveDllProc := DllProc; // Speichern der Kette von Exit-Prozeduren
  DllProc := @LibExit;	  // Installieren der LibExit-Exit-Prozedur
end.

DllProc wird immer dann aufgerufen, wenn die Bibliothek das erste Mal in den Speicher geladen wird, wenn ein Thread gestartet oder angehalten wird, oder wenn die Bibliothek aus dem Speicher entfernt wird. Die Initialisierungsteile aller von einer Bibliothek verwendeten Units werden vor dem Initialisierungscode der Bibliothek, die finalization-Abschnitte nach der Eintrittspunkt-Prozedur der Bibliothek ausgeführt.

Globale Variablen in einer Bibliothek

In einer gemeinsamen Bibliothek deklarierte globale Variablen können von Object Pascal-Anwendungen nicht importiert werden.

Eine Bibliothek kann von mehreren Anwendungen gleichzeitig genutzt werden. Jede Anwendung verfügt aber in ihrem Verarbeitungsbereich über eine Kopie der Bibliothek mit einem eigenen Satz globaler Variablen. Damit mehrere Bibliotheken (oder mehrere Instanzen einer Bibliothek) den Speicher gemeinsam nutzen können, müssen Speicherzuordnungstabellen verwendet werden. Weitere Informationen zu diesem Thema finden Sie in der Systemdokumentation.

Bibliotheken und Systemvariablen

Einige der in der Unit System deklarierten Variablen sind für Programmierer von Bibliotheken von besonderem Interesse. Mit IsLibrary können Sie feststellen, ob der Code in einer Anwendung oder in einer Bibliothek ausgeführt wird. IsLibrary ist in einer Anwendung immer False, in einer Bibliothek dagegen immer True. Während der Lebensdauer einer Bibliothek enthält die Variable HInstance das Instanzen-Handle der Bibliothek. Die Variable CmdLine ist in einer Bibliothek immer nil.

Die Variable <codeInline>DllProc</codeInline> ermöglicht einer Bibliothek die Überwachung der Betriebssystemaufrufe an ihrem Eintrittspunkt. Diese Möglichkeit wird normalerweise nur von Bibliotheken verwendet, die Multithreading unterstützen. DLLProc wird in Multithread-Anwendungen eingesetzt. Es empfiehlt sich, alle Abläufe bei der Programmbeendigung nicht über Exit-Prozeduren, sondern über finalization-Abschnitte zu steuern.

Für die Überwachung von Betriebssystemaufrufen erstellen Sie eine Callback-Prozedur mit einem Integer-Parameter. Beispiel:

procedure DLLHandler(Reason: Integer);

Außerdem müssen Sie der Variable DLLProc die Adresse der Prozedur zuweisen. Beim Aufruf der Prozedur wird ihr einer der folgenden Werte übergeben:

DLL_PROCESS_DETACH

Gibt an, dass die Bibliothek als Ergebnis einer Beendigungsprozedur oder eines Aufrufs von FreeLibrary vom Adressraum des aufrufenden Prozesses getrennt wird.

DLL_PROCESS_ATTACH

Gibt an, dass die Bibliothek eine Verbindung mit dem Adressraum des aufrufenden Prozesses herstellt. Dies ergibt sich aus einem Aufruf von LoadLibrary.

DLL_THREAD_ATTACH

Gibt an, dass der aktuelle Prozess einen neuen Thread erstellt.

DLL_THREAD_DETACH

Gibt an, dass ein Thread ohne Probleme beendet wurde.


Sie können die Aktionen im Rumpf der Prozedur davon abhängig machen, welcher Parameter an die Prozedur übergeben wird.

Exceptions und Laufzeitfehler in Bibliotheken

Wenn in einer dynamisch ladbaren Bibliothek eine Exception erzeugt, aber nicht behandelt wird, wird sie nach außen an den Aufrufer weitergegeben. Wenn die aufrufende Anwendung oder Bibliothek in Object Pascal geschrieben wurde, kann die Exception in einer normalen try...except-Anweisung behandelt werden.

In Win32 kann die Exception wie eine Betriebssystem-Exception mit dem Code $0EEDFADE behandelt werden, wenn die aufrufende Anwendung oder Bibliothek in einer anderen Programmiersprache entwickelt wurde. Der erste Eintrag im Array ExceptionInformation des Records mit der Betriebssystem-Exception enthält die Exception-Adresse, der zweite Eintrag eine Referenz auf das Exception-Objekt von Object Pascal.

Normalerweise sollten Exceptions innerhalb der Bibliothek behandelt werden. Object Pascal-Exceptions entsprechen dem Exception-Modell des Betriebssystems.

Wenn in einer Bibliothek die Unit SysUtils nicht verwendet wird, ist die Unterstützung von Exceptions deaktiviert. Tritt in diesem Fall in der Bibliothek ein Laufzeitfehler auf, wird die aufrufende Anwendung beendet. Da die Bibliothek nicht feststellen kann, ob sie von einem Object Pascal-Programm aufgerufen wurde, kann sie auch nicht die Exit-Prozeduren der Anwendung aufrufen. Die Anwendung wird einfach beendet und aus dem Speicher entfernt.

Der Shared Memory Manager

Wenn eine DLL in Win32 Routinen exportiert, die lange Strings oder dynamische Arrays als Parameter oder als Funktionsergebnis übergeben (entweder direkt oder in Records bzw. Objekten), müssen die DLL und ihre Client-Anwendungen (oder -DLLs) die Unit ShareMem verwenden. Dasselbe gilt, wenn eine Anwendung oder DLL mit New oder GetMem Speicherplatz reserviert, der in einem anderen Modul durch einen Aufruf von Dispose oder FreeMem wieder freigegeben wird. ShareMem sollte in der uses-Klausel der Programme oder Bibliotheken, von denen sie eingebunden wird, immer an erster Stelle stehen.

ShareMem ist die Interface-Unit für den Speichermanager BORLANDMM.DLL, der es Modulen ermöglicht, dynamisch zugewiesenen Speicherplatz gemeinsam zu nutzen. BORLANDMM.DLL muss mit Anwendungen und DLLs weitergegeben werden, die ShareMem einbinden. Wenn eine Anwendung oder DLL ShareMem verwendet, ersetzt BORLANDMM.DLL den Speichermanager dieser Anwendung oder DLL.

Siehe auch