1 Theorie |
Portable Programmierung versucht, Unterschiede zwischen verschiedenen Plattformen bereits bei der Programmierung zu berücksichtigen, so dass ein Quelltext mit möglichst wenigen Änderungen auf verschiedenen Zielplattformen übersetzt werden kann.
In Header-Dateien werden Konstanten zu 0 oder 1 definiert, die
angegeben, ob eine bestimmte Header-Datei oder eine Funktion
verfügbar sind.
Die Konstanten haben die Form HAVE_DATEI_H um anzuzeigen, ob
die Header-Datei datei.h vorhanden ist bzw. HAVE_FUNKTION um
anzuzeigen, ob die Funktion funktion() vorhanden ist.
Im Quelltext wird dann bedingte Compilierung eingesetzt.
In Makefiles werden meist am Beginn des Makefiles Makros für
Verzeichnisse, Bibliotheken, Compileroptionen... definiert
(Include-Suchpfad, Bibliotheks-Suchpfad, Zielverzeichnis für
Installation).
In den Regeln werden dann diese Makros verwendet.
Zur Anpassung von Compilierung und Installation ist dann nur eine
Anpassung der Makros erforderlich.
Die Konstanten vom Typ HAVE_DATEI_H und HAVE_FUNKTION werden in einer Datei config.h definiert, der Name ist variabel.
Die manuelle Suche, welche Header-Dateien, Bibliotheken und
Funktionen vorhanden sind, wäre extrem zeitaufwendig. Mit dem
Programm GNU Autoconf steht ein Mechanismus zur Vereinfachung zur
Verfügung.
Das Programm autoconf wird hier nur kurz angerissen, unter
http://www.gnu.org/software/autoconf/manual/index.html finden
Sie die vollständige Dokumentation. Eine Web-Suche nach
"autoconf tutorial" bzw. "autoconf tutorial site:de"
sollte Einführungstexte finden.
Der Entwickler schreibt in einer Datei configure.ac eine Liste der auszuführenden Tests und erstellt Vorlagen config.h.in (Vorlage für config.h) und Makefile.in (Vorlage für Makefile).
Mit dem Programm autoconf wird aus der Test-Liste configure.ac ein Script configure erstellt.
Auf der Zielplattform wird das configure-Script aufgerufen, bevor die Software erstellt wird. Ein typischer Ablauf für Erstellung und Installation portabel programmierter Software besteht aus den Kommandos:
./configure make make install
Für das configure-Script kann eine Reihe von Optionen benutzt
werden, um das Zielverzeichnis für die Installation
festzulegen...
Mit
./configure --help
werden die verfügbaren Optionen angezeigt.
In der Datei config.h.in werden die HAVE_...-Konstanten zu 0 definiert. Wollen wir z.B. testen, ob die Header-Datei unistd.h und die Funktion strtol() verfügbar sind, sieht config.h.in folgendermaßen aus:
#ifndef CONFIG_H_INCLUDED #define CONFIG_H_INCLUDED 1 /** Indikator, ob Datei vorhanden ist. */ #define HAVE_UNISTD_H 0 /** Indikator, ob Funktion vorhanden ist. */ #define HAVE_STRTOL 0 #endif
Die Testliste configure.ac würde aussehen wie folgt:
dnl configure-script AC_INIT(ex001.c) AC_CONFIG_HEADER(config.h) AC_PROG_CC AC_HEADER_CHECK(unistd.h, AC_DEFINE(HAVE_UNISTD_H)) AC_HAVE_FUNCS(strtol) AC_OUTPUT(Makefile)
Die erste Zeile (mit dnl am Anfang) ist ein Kommentar.
In der AC_INIT-Zeile muss der Name einer beliebigen
Quelltextdatei des Projektes angegeben werden. Während das
configure-Script läuft, wird geprüft, ob diese Datei vorhanden
ist.
Letztendlich wird damit getestet, ob das configure-Script im
richtigen Verzeichnis ausgeführt wird.
Die AC_CONFIG_HEADER-Zeile legt fest, dass die Datei config.h aus der Vorlage config.h.in erzeugt wird.
Die AC_PROG_CC-Zeile veranlasst, dass nach einem verfügbaren C-Compiler gesucht wird und das Makefile-Makro CC entsprechend gesetzt wird.
Die AC_HEADER_CHECK-Zeile veranlasst einen Test, ob die Header-Datei unistd.h verfügbar ist. Falls ja, wird HAVE_UNISTD_H auf 1 gesetzt.
Die AC_HAVE_FUNCS-Zeile veranlasst einen Test, ob die Funktion strtol() verfügbar ist. Falls ja, wird HAVE_STRTOL auf 1 gesezt.
Die AC_OUTPUT-Zeile legt fest, dass die Datei Makefile aus der Vorlage Makefile.in erzeugt wird.
Am Beginn der Vorlage Makefile.in stehen meist Zeilen, in denen Makefile-Makros Werte erhalten, die vom configure-Script ermittelt wurden, z.B.:
prefix=@prefix@ datarootdir=@datarootdir@ exec_prefix=@exec_prefix@ bindir=@bindir@ sbindir=@sbindir@ libexecdir=@libexecdir@ datadir=${datarootdir} sysconfdir=@sysconfdir@ scd=${sysconfdir} libdir=@libdir@ sharedlibdir=${libdir}/shared mandir=@mandir@ includedir=@includedir@ localstatedir=@localstatedir@ srcdir=@srcdir@ top_srcdir=@top_srcdir@ VPATH=@srcdir@ CC=@CC@ DEFS=@DEFS@
Im ersten Abschnitt (prefix...VPATH) werden Verzeichnisse für bestimmte Dateien konfiguriert. Die Zeilen prefix...localstatedir legen fest, in welche Verzeichnisse bestimmte Dateien bei "make install" installiert werden. Die Zeilen srcdir...VPATH geben Verzeichnisse für die Quelltexte an.
Im zweiten Abschnitt erhält zunächst das Makro CC als Wert den vom configure-Script gefundenen C-Compiler.
Das Makro DEFS wird auf "-DHAVE_CONFIG_H=1" gesetzt, dies wird
als Option dem C-Compiler immer mit übergeben.
In C-Quelltexten kann dann mit
#if HAVE_CONFIG_H #include "config.h" #else /* HAVE_...-Konstanten auf Standardwerte setzen */ #endif
geprüft werden, ob das configure-Script aufgerufen wurden. Falls
ja, wird die Header-Datei config.h verwendet.
Andernfalls werden Standardwerte für die HAVE_...-Konstanten
verwendet, die im #else-Zweig definiert werden.
Meist wird im #else-Zweig eine vorhandene Header-Datei mit den
Definitionen der Standardwerte eingebunden:
#if HAVE_CONFIG_H #include "config.h" #else #include "config-defaults.h" #endif
Die #include-Anweisungen zur Einbindung der Header-Dateien
stehen im Normalfall am Dateianfang.
Anstelle von
#include <unistd.h>
wird nun
#if HAVE_CONFIG_H #include "config.h" #else #include "config-defaults.h" #endif
#if HAVE_UNISTD_H #include <unistd.h> #endif
verwendet.
Falls die Funktion strtol() verfügbar ist, wird sie
benutzt.
Andernfalls müssen äquivalente Funktionen bzw. ein eigener Nachbau
der Funktion verwendet werden.
Im Beispiel erfolgt eine eingehende Prüfung des Textes mit
detaillierten Meldungen, falls die Funktion strtol()
verfügbar ist. Andernfalls erfolgt nur eine vereinfachte
Fehlermeldung.
/** Long-Wert aus Zeichenkette ermitteln. @param resptr Zeiger auf Variable fuer Ergebnis. @param text Text, der die Zahl enthaelt. @return 1 bei Erfolg, 0 bei Fehler. */ int long_wert_aus_text_gewinnen(long *resptr, const char *text) { int back = 0; long erg = 0L; if ((NULL != resptr) && (NULL != text)) { #if HAVE_STRTOL char *endptr = NULL; erg = strtol(text, &endptr, 10); if (endptr == text) { fprintf(stderr, "FEHLER: \"%s\"" ist keine Zahl!\n", text); fflush(stderr); } else { if (((LONG_MIN == erg) || (LONG_MAX == erg)) && (ERANGE == errno)) { fprintf( stderr, "FEHLER: \"%s\" ueberschreitet Zahlenbereich!\n", text ); fflush(stderr); } else { if ('\0' != *endptr) { fprintf( stderr, "Warnung: Unverwertbarer Text \"%s\" am Ende!\n", endptr ); fflush(stderr); } back = 1; *resptr = erg; } } #else if (1 == sscanf(text, "%ld", &erg)) { *resptr = erg; back = 1; } else { fprintf(stderr, "FEHLER: \"%s\" ist keine Zahl!\n", text); fflush(stderr); } #endif } return back; }
Unter Windows ist der autoconf-Mechanismus nicht verfügbar. Es wird daher empfohlen eine fertig vorbereitete Datei config.h für Windows in einem separaten Verzeichnis win32 mitzuliefern sowie eine Datei makefile.vc für die Nutzung mit nmake.
Üblicherweise liegen diese beiden Dateien in einem Unterverzeichnis win32.
Im Verzeichnis hsmet liegen folgende Dateien:
Um einen neuen Test hinzuzufügen, sind folgende Schritte notwendig:
autoconferstellt das configure-Script aus der Testliste.