2.1 Festes Linken oder dynamisches Laden

Auf beiden Systemen hat man zwei Möglichkeiten, um Funktionen (oder vollkommen analog: globale Variablen) einer dynamischen Bibliothek zu nutzen:

  1. Anstelle der Funktionen (oder Daten) der Bibliothek werden beim Linken leere Dummy-Objekte (sogenannte stubs) dazugelinkt, um für den Linker die Referenzen zu erfüllen. Diese stubs machen nichts, aber bringen jeweils den Namen einer Funktion in die Objektdatei.

    Beim Start des Programms wird dann vom Laufzeitsystem automatisch die Bibliothek geladen (falls sie sich nicht schon durch einen anderen Prozeß im Speicher befindet), und die Verweise auf die Dummyfunktionen werden im Zuge des Relozierens durch Verweise auf die Funktionen der Bibliothek ersetzt. Wenn die Bibliothek wiederum unbefriedigte Referenzen enthält, werden ggf. weitere Bibliotheken nachgeladen. Dann startet das Programm wie üblich.

    Aus der Sicht des Aufrufers ändert sich nichts gegenüber statisch gelinkten Funktionen. Es findet durch den Compiler wie bei anderen Funktionen auch eine statische Typkontrolle statt.

    Die Bibliothek wird also vor dem tatsächlichen Start des Programms geladen und in den virtuellen Speicher des Programms eingeblendet, und verhält sich zur Laufzeit bezüglich der verschiedenen Speichersegmente (globale Variablen, Konstanten etc.) praktisch wie eine statisch gelinkte Bibliothek; siehe dazu auch Speicherbereiche eines Prozesses.

    Diese Verwendungsart wird im folgenden festes Linken genannt.

    Genutzt wird der Mechanismus inzwischen für praktisch alle Laufzeitbibliotheken, also für Funktionen die sich selten ändern.

    Gegenüber dem statischen Linken hat man neben dem geringeren Platten- und Speicherbedarf noch den Vorteil, daß die Bibliotheken durch neuere Versionen ersetzt werden können, ohne alle Programme kompilieren zu müssen. Konkretes Beispiel: Wenn Microsoft feststellt, daß in der Socketschnittstelle doch zuviele peinliche Fehler sind, dann wird mit dem nächsten Servicepack einfach die wsock32.dll ausgetauscht, und schon sind die alten Fehler durch neue ersetzt, ohne daß jemand auch nur ein vorhandenes Programm kompilieren oder linken muß.

    Unter Windows entstehen beim Erzeugen der DLL zwei wichtige Dateien:

    1. eine Bibliotheksdatei irgendwas.lib, welche die Dummyfunktionen zum Linken der Programme enthält, und
    2. die eigentliche DLL irgendwas.dll, die den richtigen Programmcode enthält, und die beim Starten der Programme geladen wird.
    Es gibt keinen Mechanismus, der sicherstellt daß zusammengehörige LIB- und DLL-Dateien verwendet werden. Man muß nur optimistisch sein.

    Unter Unix dagegen gibt es für jedes shared object nur eine Datei; üblicherweise mit einem Namen der Art libirgendwas.so. Aus dieser Datei werden sowohl beim Linken der Programme die stubs generiert, als auch beim Starten der Programme der in den Speicher eingeblendete Maschinencode kopiert.

  2. Man kann die Funktionen der Bibliothek aufrufen, indem man sie NICHT dem Compiler und damit dem Linker bekannt macht, sondern erst nach dem Programmstart mithilfe von Betriebssystemfunktionen die Bibliothek anhand ihres Dateinamens in den Speicher laden läßt (falls dies nicht bereits durch andere Prozesse geschehen ist) und ein handle für die geladene Bibliothek erhält. Dies ist i.d.R. ein Zeiger oder eine andere ganze Zahl als Kennung für die geladene Bibliothek.

    Mithilfe dieser Kennung kann man dann mit weiteren Systemaufrufen Zeiger auf die gewünschten Funktionen beschaffen (anhand der Funktionsnamen, die als String übergeben werden). Anhand der Funktionszeiger können dann die Funktionen aufgerufen werden.

    Da solche Funktionsaufrufe vollkommen der Kontrolle von Compiler und Linker entzogen sind, muß der Programmierer sicherstellen, daß Übergabeparameter und Rückgabewerte stimmen!

    Da sich insoweit zwischen Windows und Unix nur die Namensgebung unterscheidet, folgt hier gleich eine Gegenüberstellung der zugehörigen Stichworte:
      Win32: Unix:
    Name des Mechanismus: DLL (dynamic link library) shared object
    Typ für Handle der Bibl.: HINSTANCE void*
    Funktion zum Laden: LoadLibrary() dlopen()
    Funktion zum Entladen: FreeLibrary() dlclose()
    Adresse zu suchen mit: GetProcAddress() dlsym()
    Vereinbarungen in: windows.h, winbase.h dlfcn.h



    Dieser Mechanismus wird genutzt, um sehr flexibel Programmerweiterungen zu ermöglichen. Viele komplexe Programme lassen sich so nachträglich erweitern:

    Meist stellen die aufrufenden Programme weitere Bedingungen an die geladene Bibliothek, beispielsweise eine aufzurufende Funktion mit einem definierten Verhalten.

    Tatsächlich müssen der Name der Bibliothek, die Namen und die Parameter aber erst zur Laufzeit festliegen, im Extremfall durch Benutzereingabe.

    Da solche Erweiterungen im Adreßraum des Aufrufers ausgeführt werden, sind sie einerseits so effektiv wie von vornherein eingebaute Funktionen, aber andererseits können sie bei Programmierfehlern auch ebenso leicht den gesamten Prozeß zum Absturz bringen.

    Warnung: Aus demselben Grund haben nachgeladene Laufzeitbibliotheken die selben Rechte wie das aufrufende Programm. Wer also im Browser (Internet Explorer, Netscape etc.) ein Plugin laufen läßt, lebt gefährlich, wenn der Hersteller keinen Quelltext zur Verfügung stellt anhand dessen die Funktion des Plugin eingesehen werden kann!

    Dasselbe gilt auch für COM/OLE/ActiveX-Objekte und ähnliche! Wer schön brav alle Attachments in emails löscht, ohne sie auszuführen, holt sich seine Feinde halt stattdessen mit einem Word-Dokument mit eingebetteten Objekten oder durch Laden eines Plugins für den Internet Explorer auf den Rechner.

In beiden Fällen sollte ein Debuggen unproblematisch sein, sofern das aufrufende Programm ebenso wie die Bibliothek mit den entsprechenden Compileroptionen übersetzt wurden.

www.wachtler.de