17.2 Bibliotheken erstellen
Zu Beginn der Programmiererlaufbahn verwendet man Bibliotheken, die von einer Distribution oder von anderen Programmierern zur Verfügung gestellt werden. Aber irgendwann wird es Sie packen, eine eigene Bibliothek zu entwerfen; sei es nun, um der Open-Source-Gemeinde auch etwas zurückzugeben, oder aus Eigenbedarf, um immer wiederkehrende Routinen endlich ein für alle Mal in einer Bibliothek zu vereinen. Hierzu stehen Ihnen zwei Möglichkeiten zur Verfügung – das Erstellen einer statischen oder einer dynamischen Bibliothek. Welche von beiden Methoden die bessere für welchen Anwendungsfall ist und wie Sie diese erstellen können, erfahren Sie jetzt in den folgenden Abschnitten.
17.2.1 Statische Bibliotheken erstellen
Wenn Sie in Ihrem Programm eine statische Bibliothek verwenden wollen, müssen Sie diese namentlich beim Linken angeben. Damit steht dem Programm das komplette Archiv zur Verfügung. Dieses Vorgehen hat allerdings den Nachteil, dass mehr Platz sowohl auf der Festplatte als auch im Hauptspeicher dafür nötig ist, weil statische Bibliotheken mehrfach vorhanden sein könnten – sprich in allen Programmen, die ebenfalls diese statische Bibliothek verwenden. Allerdings hat diese Methode auch Vorteile, sonst würde man sie ja schließlich nicht verwenden. Da alle Funktionen der Bibliothek dem Programm hinzugefügt werden, stehen somit auch alle jederzeit sofort zur Verfügung.
Im Folgenden möchte ich Ihnen das Erstellen einer statischen Bibliothek in einfachster Weise zeigen. Dies kann jederzeit auch mit umfangreicheren Projekten gemacht werden. Sie benötigen dazu folgende Dateien:
|
Eine Headerdatei mit den Funktionsdefinitionen, symbolischen Konstanten und eventuell globalen Variablen. |
|
Mindestens eine Quelldatei, in der die einzelnen Funktionen kodiert werden – sprich, deren Ausführung programmiert wird. Aus dieser Quelldatei erstellen Sie anschließend die statische Bibliothek. Diese Datei benötigt somit auch keine main()-Funktion. |
Als Beispiel wird hier eine einfache Bibliothek erstellt, die lediglich zwei Integer miteinander vergleicht und bei Gleichheit 1 (MYTRUE) oder Ungleichheit 0 (MYFALSE) zurückliefert. Die Headerdatei myequal.h sieht wie folgt aus:
/* myequal.h */
#ifndef _MYEQUAL_H_
#define _MYEQUAL_H_
#define MYTRUE 1
#define MYFALSE 0
typedef int MYBOOL;
MYBOOL my_equal( int a, int b );
#endif
Jetzt fehlt noch die kodierte Funktion, woraus Sie anschließend die Bibliothek erstellen werden:
/* myequal.c */
#include <stdio.h>
#include "myequal.h"
MYBOOL my_equal( int a, int b ) {
if( a == b )
return MYTRUE;
return MYFALSE;
}
Um jetzt aus der Quelldatei myequal.c eine Bibliotheksdatei zu machen, müssen Sie zuerst eine Objektdatei daraus erzeugen:
$ gcc -c myequal.c
$ ls -l
insgesamt 12
-rw-r--r-- 1 tot users 125 2004–04–08 01:12 myequal.c
-rw-r--r-- 1 tot users 158 2004–04–08 01:11 myequal.h
-rw-r--r-- 1 tot users 644 2004–04–08 01:18 myequal.o
Jetzt müssen Sie im nächsten Schritt ein Archiv erstellen. Sie wissen ja seit dem Kapitel über GCC, dass Archivdateien von statischen Bibliotheken meistens immer mit lib beginnen, gefolgt vom Namen der Bibliothek, und mit .a enden (z. B. libname.a). Somit wird im Folgenden aus der Objektdatei myequal.o eine Archivdatei mit dem Namen libmyequal.a erstellt werden. Bei der Vergabe des Namens einer Bibliothek sollten Sie sicherstellen, dass es keine Bibliothek mit diesem Namen gibt.
Eine solche Archivdatei können Sie mit dem Befehl ar (Archiver) erzeugen. Damit können Sie Archive erstellen und verändern.
$ ar crs libmyequal.a myequal.o
$ ls -l
insgesamt 16
-rw-r--r-- 1 tot users 790 2004–04–08 01:19 libmyequal.a
-rw-r--r-- 1 tot users 125 2004–04–08 01:12 myequal.c
-rw-r--r-- 1 tot users 158 2004–04–08 01:11 myequal.h
-rw-r--r-- 1 tot users 644 2004–04–08 01:18 myequal.o
Mit der Option r sorgen Sie dafür, dass bei einem bereits bestehenden Archiv die ältere Version der Objektdatei durch eine neuere Version ersetzt wird, und mit der Option c wird erzwungen, dass ein Archiv angelegt wird, falls es noch nicht existiert. Soll ein Index hinzugefügt werden, so kann s mit hinzugegeben werden. s macht allerdings erst Sinn, wenn man weitere Objektfiles in ein bereits existierendes Archiv aufnimmt.
Jetzt haben Sie zwar ein Archiv in Ihrem Verzeichnis, aber der GCC weiß noch nichts davon, wenn Sie diesen mit dem Flag -l aufrufen würden. Daher muss diese Bibliothek noch einem Index, wo alle Funktionen eines Archivs eingetragen sind, hinzugefügt werden. Mithilfe des Indexeintrags werden die Zugriffe auf Funktionen im Archiv optimiert. Dieser Eintrag wird mit einem einfachen Aufruf von ranlib erledigt.
$ ranlib libmyequal.a
Jetzt können Sie die Bibliothek installieren. Dazu müssen Sie nur noch die Bibliothek libmyequal.a in das Verzeichnis /usr/lib oder /usr/local/lib und die Headerdatei myequal.h in das Verzeichnis /usr/include oder /usr/local/include kopieren.
Hinweis Die Verzeichnisse /usr/lib und /usr/include bzw. eigentlich alle Pfade außer /opt, die nicht irgendwie ein »local« enthalten, sind für das System aus organisatorischen Gründen vorbehalten. Somit kann man immer gleich unterscheiden: /usr/include/popt.h => RPM-Paket, /usr/local/include/popt.h => eigener Weg. Natürlich befolgen nicht alle Source-Pakete unbedingt diese »local«-Technik.
Hinweis Sie müssen die Bibliothek natürlich nicht in ein entsprechendes Verzeichnis installieren. Sie können natürlich auch jedes andere Verzeichnis hierzu verwenden, nur müssen Sie beim Übersetzen angeben, wo sich die Include-Datei (-I) und wo sich die Bibliothek befindet (-L). Außerdem empfiehlt es sich natürlich in der Praxis, den kompletten Vorgang mit einem Makefile zusammenzustellen. Statt -L ist natürlich auch der Pfad zur Bibliothek möglich, z. B.: gcc -o prog strap.o libdir/myequal.a.
|
# cp -p libmyequal.a /usr/local/lib
# cp -p myequal.h /usr/local/include
Sofern die eben erwähnte »local«-Technik bei Ihnen nicht funktioniert, können Sie auch das local weglassen und die Dateien nach /usr/lib bzw. /usr/include kopieren. Am Ende können Sie nochmals ranlib über das Archiv laufen lassen, falls sich etwas verändert hat.
Hierzu ein einfaches Listing, das die Verwendung der neu erstellten statischen Bibliothek zeigt.
/* testlist.c */
#include <stdio.h>
#include <myequal.h>
int main(void) {
MYBOOL ret;
ret = my_equal ( 5 , 5 );
if( ret == MYTRUE )
printf("Beide Werte sind gleich\n");
else
printf("Die Wert sind nicht gleich\n");
return 0;
}
Das Programm bei der Ausführung:
$ gcc -o testlist testlist.c -lmyequal
$ ./testlist
Beide Werte sind gleich
17.2.2 Dynamische Bibliotheken (Shared Libraries) erstellen
Dynamische Bibliotheken (auch Shared Libraries genannt) werden im Gegensatz zu den statischen Verwandten nicht zum Programm hinzugebunden, sondern erst beim Aufruf des Programms aus einem Bibliotheksverzeichnis (gewöhnlich /usr/lib bzw. /usr/local/lib) in den Speicher geladen und dynamisch an das Programm gebunden. Verwendet nun ein weiteres Programm eine Funktion aus dieser Bibliothek, muss diese nicht erneut in den Speicher geladen werden, sondern es wird die bereits verwendete Instanz in den Adressraum des zweiten Programms eingeblendet. Dies hat auch den Vorteil, dass die Programme etwas schlanker sind – und das trifft sowohl auf deren Größe als auch auf den Speicherverbrauch zu. Denn zum einen ist die Bibliothek nicht im Programmcode eingebunden, und zum anderen wird der Hauptspeicherbedarf dadurch gering gehalten, dass nur eine Instanz der Bibliothek benötigt wird. Nachteil: Befindet sich eine Bibliothek noch nicht im Speicher, kann beim ersten Laden dieser Bibliothek der Programmstart ein wenig länger dauern, weil Shared Libraries immer vollständig geladen werden, während bei der Kompilierung nur Objektdateien aus dem Archiv ausgewählt werden, in denen auch betroffene Symbole (Funktionen, Variablen etc.) vorkommen. Allerdings ist diese »längere Dauer« kaum bemerkbar und eigentlich auch nicht erwähnenswert.
Ein wichtiger Aspekt bei dynamischen Bibliotheken ist die Abwärtskompatibilität zu den vorhergehenden Versionen. Die Veränderungen sollten dabei durch die Erhöhung der Unterversionsnummer gekennzeichnet werden. Sollte sich etwas gravierend ändern, kann auch die Hauptversionsnummer verändert werden. Der Aufbau von solchen Bibliotheken sieht wie folgt aus:
lib(name).so.(Hauptversionsnr.)(Unterversionsnr.)(Releasenr.)
Folgende Regeln gilt es dabei einzuhalten: Werden Fehler behoben, ändert sich die Release-Nummer. Werden neue Funktionen hinzugefügt, doch die Bibliothek bleibt trotzdem abwärts kompatibel, dann sollte die Unterversionsnummer verändert werden. Ist die neue Bibliotheksversion hingegen nicht mehr abwärts kompatibel, wird die Hauptversionsnummer verändert. Nicht mehr abwärts kompatibel heißt:
|
Eine Funktion der Vorgängerversion wurde verändert oder entfernt. |
|
Wenn sich Funktionen anders verhalten als in der alten Version. |
|
Wenn sich Datenstrukturen grundlegend verändert haben; wurde z. B. in einer Datenstruktur der Datentyp verändert. |
Da allerdings nicht alle Systeme die gleichen Versionen von Bibliotheken verwenden, wird nach dem Kopieren einer neuen Bibliotheksversion ein entsprechendes Verzeichnis ldconfig aufgerufen. Dieses Programm legt für eine Bibliothek mit Versionsnummer einen symbolischen Link an, der nur den Namen der Bibliothek (ohne Versionsnummer) und einen Link auf den Namen der Bibliothek mit der Hauptversionsnummer (ohne Unterversionsnummer und Release-Nummer) enthält. Einen Überblick zu allen eingerichteten symbolischen Links können Sie sich mit folgendem Aufruf verschaffen:
# ldconfig -p
887 libs found in cache `/etc/ld.so.cache'
libzvt.so.2 (libc6) => /opt/gnome/lib/libzvt.so.2
libzvt-2.0.so.0 (libc6) => /opt/gnome2/lib/libzvt-2.0.so.0
libz.so.1 (libc6) => /lib/libz.so.1
libz.so (libc6) => /usr/lib/libz.so
liby2util.so.3 (libc6) => /usr/lib/liby2util.so.3
liby2pm.so.2 (libc6) => /usr/lib/liby2pm.so.2
liby2.so.2 (libc6) => /usr/lib/liby2.so.2
libyui.so.2 (libc6) => /usr/lib/libyui.so.2
...
ldconfig sucht immer nach allen dynamischen Bibliotheken, die in einem bestimmten Verzeichnis liegen, und erstellt bei Bedarf einen symbolischen Link auf die jeweilige Version. Also eignet sich ldconfig prima zum Austausch von verschiedenen Bibliotheksversionen. Bitte beachten Sie, dass ldconfig nur mit Superuser-Rechten ausgeführt werden kann.
Hinweis In welchem Verzeichnis ldconfig sucht, wird in der Datei /etc/ld.so.conf beschrieben. Sofern Sie weitere Verzeichnisse hinzufügen wollen, müssen Sie hier nur einen entsprechenden Eintrag hinzufügen.
|
Hierzu folgen jetzt die einzelnen Schritte, die gewöhnlich verwendet werden, um eine dynamische Bibliothek zu erzeugen und auch zu installieren. Als Beispiel dient wieder dasselbe Listing, wie Sie es schon beim Erstellen von statischen Bibliotheken verwendet haben.
Zuerst müssen Sie die Quelldatei kompilieren, um eine Objektdatei daraus zu erzeugen:
$ gcc -fPIC -Wall -g -c myequal.c
Hierbei wurde zusätzlich die Option -fPIC verwendet, um positionsunabhängigen Code zu erzeugen. Dies verhindert, dass im Maschinencode eine feste Adresse dafür vergeben wird und somit die dynamische Bibliothek an (fast) jeder beliebigen Stelle des Adressraums vom Programm eingebunden werden kann.
Jetzt zum Linken der Objektdatei:
$ gcc -ggdb3 -shared -Wl,-soname,libequal.so.1 –o \
libequal.so.1.0 myequal.o -lc
Mit -shared geben Sie an, dass hier eine dynamische Bibliothek erzeugt werden soll. -Wl leitet die Optionen an den Linker ld weiter. Für -soname wird der vollständige Bibliotheksname mit den Versionsnummern angegeben. Mit myequal.o geben Sie die Objektdatei an, die in dieser dynamischen Bibliothek aufzunehmen ist. Hierbei können selbstverständlich mehrere Objektdateien aufgelistet werden. Mit -lc linken Sie noch die C-Bibliothek hinzu, was Sie in der Praxis immer machen sollten. Natürlich können auch hierbei noch mehr Bibliotheken hinzugelinkt werden. Hier nochmals die Syntax dazu:
gcc -g -shared – Wl,-soname,soname -o bibliotheksname \
Objektdatei(en) Bibliothek(en)
Beim anschließenden Linken mit einer Bibliothek (über -l) sucht der Linker sich dann die entsprechende Datei, z. B. /usr/lib/libbibliotheksname.so, sieht sich diese an (dass es ein Symlink ist, wird ignoriert) und schaut nach dem DT_SONAME-Tag, das den Dateinamen beinhaltet, der in die Executable eingetragen werden soll.
Bei einem Vergleich zwischen den dynamischen Bibliotheken und den statischen fällt der Größenunterschied erheblich auf:
$ ls -l
insgesamt 36
-rwxr-xr-x 1 tot users 11634 2004–04–08 04:26 libequal.so.1.0
-rw-r--r-- 1 tot users 790 2004–04–08 01:38 libmyequal.a
Jetzt benötigen Sie Superuser-Rechte, um die dynamische Bibliothek in das Verzeichnis /usr/lib bzw. /usr/local/lib zu kopieren. Wechseln Sie am besten gleich in das Verzeichnis:
# cp libequal.so.1.0 /usr/lib
# cd /usr/lib
Erzeugen Sie jetzt einen symbolischen Link, den Sie für den Linker beim Übersetzen benötigen, wenn Sie die dynamische Bibliothek mit dem Flag -l angeben:
/usr/lib # ln -fs libequal.so.1.0 libequal.so.1
/usr/lib # ln -fs libequal.so.1 libequal.so
Zu guter Letzt müssen Sie jetzt noch ldconfig aufrufen:
# ldconfig
Das sollte genügen, um mit der dynamischen Bibliothek arbeiten zu können. Jetzt können Sie wieder dasselbe Listing wie schon im Beispiel der statischen Bibliothek verwenden und folgendermaßen übersetzen:
$ gcc -o testlist testlist.c –lequal
Hinweis Haben Sie auf Ihrem System sowohl eine statische als auch eine dynamische Version von libequal.so und libequal.a, bindet der Compiler, sofern Sie nichts anderes angeben wird, standardmäßig immer die dynamische Version hinzu – was natürlich durch Linker-Skripte geändert werden kann. Ein Blick in z. B. /usr/lib/libc.so zeigt so manches mehr.
|
17.2.3 Dynamisches Nachladen von Bibliotheken
Außer dem automatischen Nachladen und Binden von Bibliotheken können Sie dynamische Bibliotheken auch zur Laufzeit des Programms nachladen. Hierfür sind folgende Funktionen nötig:
#include <dlfcn.h>
void *dlopen(const char *filename, int flags);
const char *dlerror(void);
void *dlsym(void *handle, const char *symbol);
int dlclose(void *handle);
Mit der Funktion dlopen() öffnen Sie eine dynamische Bibliothek filename. Bei Erfolg wird ein Zeiger auf die geöffnete Bibliothek zurückgegeben oder im Fehlerfall NULL. Sind in der Bibliothek Symbole, die nicht aufgelöst werden können (eine Bibliothek kann wie oben erwähnt ja eine andere noch benötigen), hängt das Verhalten vom flag ab. Folgende Flags können hierfür verwendet werden:
RTLD_LAZY – Undefinierte Symbole werden aufgelöst, wenn eine Funktion in der dynamischen Bibliothek ausgeführt wird.
RTLD_NOW – dlopen() – Kehrt erst dann zurück, wenn alle undefinierten Symbole aufgelöst werden konnten.
RTLD_GLOBAL – Dieses Flag wird mit dem bitweisen ODER und mit einem der eben erwähnten Flags verwendet. Ist dieses angegeben, so sind die Symbole aus der geladenen Bibliothek den nachfolgenden geladenen Bibliotheken ebenfalls bekannt. Es gibt sozusagen zwei Symbolräume, einmal für die Hauptanwendung und einmal die Library.
Tritt ein Fehler auf, können Sie mit der Funktion dlerror() einen String ausgeben lassen, der den zuletzt aufgetretenen Fehler genauer umschreibt.
Mit der Funktion dlsym() bekommen Sie bei Erfolg die Adresse des gewünschten Symbols, das sich in der Bibliothek handle befindet, zurück (man kann nämlich nicht nur Funktionen mit dl*() holen!). Wenn symbol nicht aufgelöst werden konnte, wird NULL zurückgegeben.
Der Speicher, der auf die Bibliothek handle verweist, kann mit der Funktion dlclose() unter Angabe der Bibliothek handle wieder freigegeben werden. (Der Memory Debugger GXmalloc (Eigenkreation des Fachgutachters) verrät, dass dlclose() nicht alles freigibt, was dlopen() sich geholt hatte. Aber es handelt sich dabei um wenige Bytes pro Bibliothek.) Außerdem wird ein Zähler, der die Anzahl der geöffneten Bibliotheken von dlopen() mitzählt, wieder dekrementiert. Ist dieser Zähler 0, wird die Bibliothek aus dem Speicher entfernt.
Hinweis Zum Verwenden der hier vorgestellten Funktionen müssen Sie die Bibliothek libdl.a bzw. libdl.so (statische oder dynamische Version) mit dem Flag -ldl hinzulinken.
|
Das folgende Beispiel lädt die zuvor selbst geschriebene dynamische Bibliothek libequal.so während der Laufzeit des Programms hinzu. Anschließend wird die Funktion my_equal() geladen (genauer, die Adresse der Funktion ermittelt) und zwei Zahlen miteinander so lange verglichen, bis Sie für einen der Werte 0 angeben. Am Ende wird jeweils der Speicher wieder freigegeben. Zwar hat unsere dynamische Bibliothek nur eine Funktion, die verwendet werden kann, dennoch können Sie beinahe beliebig viele (vorhandene) Funktionen einer Bibliothek dynamisch dazuladen. Die einzelnen Funktionen wurden so geschrieben, dass Sie diese auch ohne Probleme bei einem anderen Projekt mit einer anderen Bibliothek verwenden können.
/* dynamisch.c */
#include <stdio.h>
#include <stdlib.h>
#include <myequal.h>
#include <dlfcn.h>
#define LIBEQUAL "libequal.so"
/* dynamische Bibliothek laden */
static void *my_load_dyn (const char *lib) {
static void *handle;
handle = dlopen (LIBEQUAL, RTLD_NOW);
if (handle == NULL) {
printf ("Fehler bei dlopen(): %s\n", dlerror ());
exit (EXIT_FAILURE);
}
return handle;
}
/* Funktion aus der dynamischen Bibliothek laden */
static void *my_load_func (void *handle, const char *func) {
void *funcptr = dlsym (handle, func);
if (funcptr == NULL) {
printf ("Fehler bei dlsym(): %s\n", dlerror ());
exit (EXIT_FAILURE);
}
return funcptr;
}
/* Speicher wieder freigeben */
static void my_close_func (void *handle) {
if (dlclose (handle))
printf ("Fehler bei dlclose(): %s\n", dlerror ());
}
int main (void ) {
MYBOOL ret;
void *lib_handle;
int (*equal) (int a, int b);
int val1=1 ,val2 = 1;
while ( 1 ) {
printf ("Zahl 1 eingeben: ");
scanf ("%d", &val1);
printf ("Zahl 2 eingeben: ");
scanf ("%d", &val2);
if( val1 == 0 || val2 ==0 )
break;
lib_handle = my_load_dyn (LIBEQUAL);
equal = my_load_func (lib_handle, "my_equal");
ret = (*equal) (val1, val2);
if (ret == MYTRUE)
printf ("Beide Werte sind gleich\n");
else
printf ("Die Werte sind nicht gleich\n");
my_close_func (lib_handle);
}
return EXIT_SUCCESS;
}
Das Programm bei der Ausführung:
$ gcc -o dynamisch dynamisch.c -ldl
$ ./dynamisch
Zahl 1 eingeben: 3
Zahl 2 eingeben: 4
Die Werte sind nicht gleich
Zahl 1 eingeben: 5
Zahl 2 eingeben: 5
Beide Werte sind gleich
Zahl 1 eingeben: 1
Zahl 2 eingeben: 0
Hinweis Dass die Bibliothek im Beispiel immer von neuem geladen wird, dient nur zur Demonstration – sprich, Sie können sich hierbei gerne eine Abfrage einbauen, die ermittelt, welche Bibliothek Sie sich im nächsten Durchgang laden wollen. Es soll praktisch damit gezeigt werden, dass jederzeit – während der Laufzeit des Programms – eine Bibliothek geladen und am Ende wieder freigegeben werden kann. In der Praxis sollten Sie das dlopen() natürlich noch vor das while() setzen.
|
|