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:
- Oeffnen einer Bibliothek,
- Suchen einer beliebigen Anzahl von Symbolen in dieser Bibliothek,
- Auftretende Fehler behandeln und
- Schliessen der Bibilothek.
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:
- in den Directories, die in der Environment-Variable LD_ELF_LIBARY_PATH (durch
Semikolons getrenne) angegeben sind. Wenn diese Variable nicht existiert, wird
LD_LIBARY_PATH durchsucht,
- die Bibliotheken, die in der Datei /etc/ld.so.conf aufgefuehrt sind,
- im Directory /usr/lib und
- im Directory /lib
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