Dynamisches Laden zur Laufzeit (shared objects)


Groessere Softwarepakete werden unter Linux/Unix ueblicherweise in Module zerlegt, die getrennt voneinander entwickelt werden. Manchmal sind diese Module eigenstaendige Programme, die mit anderen Modulen des Softwarepakets ueber Pipes oder andere Formen der Interprozesskommunikation kommunizieren. Eine andere Moeglichkeit der Kommunikation ist die Implementierung von sogenannten shared objects (geteilte Objecte). Solche shared objects koenen entweder Objectdateien oder dynamische Bibliotheken sein. da der Linker nicht von den shared objects wissen muss, ist es noch nicht einmal erforderlich, dass diese zum Zeitpunkt des Linkens existieren muessen. Ein weiterer Unterschied von shared objects zu dynamischen Bibliotheken ist, dass sie anders installiert werden wie die meisten dynamischen Bibliotheken.

Daneben muessen die von shared objects verwendeten Symbolnamen nicht eindeutig und einmals sein, was sie meist auch nicht sind, da verschiedene shared objects, die fuer die gleiche Schnittstelle entwickelt wurden, normalerweise auch Eintrittspunkte mit den gleichen Namen verwenden, was bei dynamischen Bibliotheken absolut unmoeglich ist.

Die haeufigste Anwendung von shared objects sind sogenannte generische Schnittstellen. Diese snid im Prinzip nicht anderes als Funktionszeiger, denen erst zur Laufzeit die Adresse der entsprechenden Funktion zugewiesen wird. So ist es moeglich, dass Programme beliebig erweiterbar sind, ohne das sie erneut kompiliert oder gelinkt werden muessen.
Ein Beispiel fuer die Verwendung von generischen Schnittstellen koennte ein Programm sein, dass Simulationen fuer Industrieprozesse nach verschiedenen Verfahren durchfuehren kann. Diese Programm verwendet intern ein eigenes Format, um die berechneten Werte grafisch am Bildschrim darzustellen. Wird nur eine generische Schnittstelle geschafft, die die Durchfuehrung der Simulation in zur Laufzeit geladene shared objects verlagert, kann jederzeit ein neues Simulationsverfahren hinzugefuegt werden, ohne das diese Programm neu kompilier/gelinkt werden muss. Generische Schnittstellen setzen allerdings immer eine gute Dokumentation ihrer Funktionsweise voraus, damit auch andere Programmierer, die die Interna des jeweiligen aufrufenden Hauptprogramms nicht kennen, sie benutzen und so den Funktionsumfang des Hauptprogramms erweitern koennen.
Dynamisches Laden erfordert folgende Aktivitaeten: Die hierzu notwendigen Funktionen dlopen, dlsym, dlerror und dlclose sind in der Headerdatei <dlfcn.h> deklariert.
1 #include <dlfcn.h>
2 void *dlopen(const char *filename, int flag);
3 void *dlsym(void *handle, char *symbol);
4 const char *dlerror(void);
5 int dlclose(void *handle);

dlopen

dlopen laedt die dynamische Bibliothek, deren Name ueber den Parameter filename angegeben ist und gibt einen Zeiger zurueck, mit dem nun Zugriffe (mit den Funktionen dlsym und dlclose) auf diese Bibliothek moeglich sind. Wird fuer filename ein absoluter Pfad (beginnen mit /) angegeben, muss dlopen die Bibliothek nicht suchen. Die ist der uebliche Weg um dlopen aufzurufen. Ist der fuer filename angegebener Pfad kein absoluter Pfad, sucht dlopen die entsprechende Bibliothek an den folgenden Stellen in der angegebenen Reihenfolge: Gibt man fuer filename den NULL-Zeiger an, oeffnet dlopen die Datei des aktuell ausgefuehrten Programms (was aber nur in sehr wenig Faellen sinnvoll ist).
Undefinierte externe Referenzen in der dynamischen Bibliothek werden aufgeloest, indem andere zuvor mit RTLD_GLOBAL geoeffnete Bibliotheken und die Bibliotheken durchsucht werden, die in der Abhaengigkeitsliste dieser Bibliothek enthalten sind.
Fuer flag kann eine der folgenden Konstanten angegeben werden:
RTLD_LAZY
Undefinierte Symbole in der dynamischen Bibliothek werden erst dann aufgeloest, wenn der Code dieser dynamischen Bibliothek ausgefuehrt wird.
RTLD_NOW
Alle undefinierten Symbole in der dynamischen Bibliothek werden aufgeloest, bevor die Funktion dlopen zurueckkehrt. Wenn das nicht moeglich ist, liefert dlopen den Rueckgabewert NULL. Dieses Flag wird meist waehrend der Entwickliung und Fehlersuche gesetzt, denn so wird man sofort ueber unaufgeloeste Referenzen in shared objects informiert.
RTLD_GLOBAL
In diesem Fall werden die hier definierten externen Symbole den Bibliotheken, die nachfolgend geladen werden, zur Verfuegung gestellt.
Hinweise:
Enthaelt eine dynamische Bibliothek eine Funktion namens _init, wird diese ausgefuehrt, bevo dlopen zurueckkehrt.
Wird die gleiche Bibliothek mehrmals geoeffnet, dann wird immer der gleiche Zeiger (handle) zurueckgegeben.
Mit bitweisem OR (|) kann RTLD_GLOBAL mit RTLD_NOW und RTLD_LAZY verknuepft werden.

dlsym

dlsym sucht in der Bibliothek handle, was der Rueckgabewert der zuvor mit dlopen erfolgreich geoeffneten Bibliothek sein muss, nach dem Symbol mit dem Namen symbol. dlsym liefert die Adresse, an die dieses Symbol geladen wurde, oder, falls dieses nicht gefunden werden koennte, den NULL-Zeiger. Da es aber Symbole geben kann, die die Adresse NULL haben, laesst dieser Rueckgabewert nicht unbedingt auf einen Fehler schliessen. Deswegen ist es in diesem Fall empfehlenswert, die nachfolgende Funktion dlerror heranzuziehen, um eine Fehlerueberpruefung durchzufuehren.

dlerror

gibt NULL zurueck, wenn kein Fehler seit dem Oeffnen der dynamischen Bibliothek oder seit dem letzten Aufruf von dlerror aufgetreten ist, oder aber die Adresse der entsprechenden Fehlermeldung. Da jeder Aufruf von dlerror dazur fuehrt, dass eine eventuell vorhandene Fehlermeldung nach diesem Aufruf nicht mehr zur Verfuegung steht, sollte man diese Fehlermeldung in einer eigenen Variable speichern, wenn man sie fuer spaetere Zwecke wieder benoetigt.

dlclose

Jedesmal wenn dlopen eine Bibliothek oeffnet, wird ein internet Referenzzaehler erhoeh. Dieser Referenzzaehler wird bei jedem Aufruf von dlclose um 1 erniedrigt. Erst wenn dieser Referenzzaehler bedingt durch einen dlclose-Aufruf 0 wird, wird auch die Bibliothek geschlossen und der fuer sie allokierte Speicherplatz freigegeben.
Enthaelt die dynamische Bibliothek eine Funktion namens _fini, wird diese ausgefuehrt, bevor dlclose zurueckkehrt. Durch den Referenzzaehler ist es moeglich, beliebig oft die entsprechende Bibliothek zu oeffnen und zu schliessen, ohne sich darum kuemmern zu muessen, ob die zugehoerigen shared objects bereits vom aufrufenden Code geladen wurden. Wenn diese Funktionen in einem Programm verwendet werden, muss man beim linken dieses Programms die Bibliothek libdl.so mit der Option -ldl dazulinken.
Das Programm sinus.c demonstriert das dynamische Laden eines shared object, indem es die Funktion sin aus der mathematischen Bibliothek laedt, um sie im Programm verwenden zu koennen. Nachdem man das Programm mit
$ cc -o sinus sinus.c fehler.c -ldl
kompiliert und gelinkt hat, kann man es starten, indem man ihm den Pfadnamen der mathematischen Bibliothek als Argument auf der Kommandozeile uebergibt:
1 $ ./sinus /usr/lib/libm.so
2 0.000000
3 0.099833
4 0.198669
5 0.295520
6 0.389418
7 0.479426
Nun soll aber eine eigene Funktion sin erstellt werden, die nicht den Sinus berechnet, sondern nur das Doppelte des uebergebenen Werts zurueckliefert, wie dies in folgendem Programm umgesetzt wird:
#include <stdio.h>
double sin(double wert)
{
  return(2*wert);
}
Aus diesem Programm (sin.c) wird mit den folgenden Aufrufen eine dynamische Bibliothek erstellt:
1 $ gcc -fPIC -Wall -g -c sin.c
2 $ gcc -g -shared -WL,-soname,sin.so.1 -o sin.so.1.0 sin.o -lc
3 $ ln -s sin.so.1.0 sin.so.1
Startet man das Programm sinus erneut und uebergibt ihm aber diesmal die eigene dynamische Bibliothek sin.so.1 im Working-Directory, verwendet es nur das shared object sin aus der eigenen Bibliothek:
1 $ export LD_LIBRARY_PATH=.
2 $ ./sinus sin.so.1.0
3 0.000000
4 0.200000
5 0.400000
6 0.600000
7 0.800000
8 1.000000

Hier sieht man also, dass beim Arbeiten mit shared objects also ein einfaches Austauschen von Funktionen moeglich ist, ohne dass die Originalprogramme erneut kompiliert und/oder gelinkt werden muessen.

Zurueck || Index || Vor