Libraries erstellen und verwenden

Eine Library ist eine Sammlung von Programmfunktionen, die nicht zu einem Programm kompiliert werden, sondern nach dem Kompilieren als eine Art Werkzeugkiste zur Verfügung stehen. Ein Programm kann anschließend die Funktionen der Library verwenden, ohne diese selbst mitkompilieren zu müssen.

Dies macht beispielsweise da Sinn, wo verschiedene Programme auf ähnliche Funktionalitäten zurückgreifen. Würde jede Software, die auf einem bestimmten GUI-Toolkit basiert dieses Toolkit komplett mitkompilieren, würde die Binärdatei der Anwendung sehr groß. Die nächste Software, die auf dem gleichen Toolkit basiert würde es wieder für sich mitkompilieren usw. Ändern sich nun Internas des Toolkits, müssten alle Anwendungen die es verwenden neu kompiliert werden. Zu welchem enormen Aufwand dies führt, liegt auf der Hand.

Die klassische Lösung ist, Programmteile (wie z.B. ein GUI-Toolkit) über eine externe Bibliothek bereitzustellen. Dabei kann es sich um eine Statische Library oder eine Dynamische Library handeln.

Ausgangssituation

Das Erstellen und Verwenden von Libraries schauen wir uns dabei an folgendem kleinen Beispiel an: Wir haben eine Point-Klasse implementiert, um mit Punkten im dreidimensionalen Raum arbeiten zu können.

Datei: point.hpp

#ifndef POINT_INCLUDE_GUARD
#define POINT_INCLUDE_GUARD
 
class Point {
    protected:
        float x, y, z;
    public:
        Point(float x, float y, float z);
        virtual ~Point();
        float getX();
        float getY();
        float getZ();
        void setX(float x);
        void setY(float y);
        void setZ(float z);
};
 
#endif

Datei: point.cpp

#include "point.hpp"
 
#include <iostream>
 
Point::Point(float x, float y, float z)
    : x(x)
    , y(y)
    , z(z) {
    std::cout << "<Point@" << this << "> created" << std::endl;
}
 
Point::~Point() {
    std::cout << "<Point@" << this << "> destroyed" << std::endl;
}
 
float Point::getX() {
    return this->x;
}
 
float Point::getY() {
    return this->y;
}
 
float Point::getZ() {
    return this->z;
}
 
void Point::setX(float x) {
    this->x = x;
}
 
void Point::setY(float y) {
    this->y = y;
}
 
void Point::setZ(float z) {
    this->z = z;
}

Soweit so gut. Wollen wir nun unsere Klasse verwenden … normalerweise inkludieren wir die Headerdatei und kompilieren die Quellcodedatei. Wir wollen aber an dieser Stelle eine Library erzeugen, die wir in mehreren Programmen verwenden können. Dazu müssen wir die Arten von Libraries kennen:

Statische Library

Beginnen wir mit statischen Libraries. Sie werden fest zum Programm kompiliert, so dass am Schluss nur eine ausführbare Datei entsteht, die Programm und Library enthält.

Eine statische Library ist eine einfache Sammlung von Object Files, die in einem Paket zusammengepackt werden. Ihr Vorteil ist, dass sie einfach zum Programm hinzugelinkt werden können, ohne dass man sie laufend neu kompilieren muss. Dieser Vorteil mit der Verwendung eines Build-Systems wie Make überholt, dennoch eignen sie sich, um Teilprojekte auszugliedern, in größeren Projekten Aufgaben zusammenzufassen und dadurch, dass weniger Dateien zusammengelinkt werden müssen, die Arbeitszeit des Linkers etwas zu beschleunigen.

Im Vergleich zu dynamischen Librarys sind sie geringfügig schneller, da die Aufrufe an statische Adressen erfolgen können.

Statisches Linken

Dynamische Library

Unter Windows haben sogenannte DLL-Dateien ihren Ruf weg: sie fehlen, sind veraltet oder irgendetwas konnte nicht geladen werden. Aber lassen wir uns von den Schattenseiten nicht verunsichern. Wie bereits gesagt begegnet man unter Windows sogenannten DLL-Dateien (kurz für Dynamic Link Library). Arbeitet man mit linux-basierten Systemen, stößt man auf so-Dateien (kurz für Shared Objects). Intern unterscheiden sich beide Dateien, so dass native DLLs nicht auf linux-basierten Systemen verwendet werden können - gleiches gilt für Shared Objects unter Windows. Jedoch ist die Idee hinter beiden gleich: Programmteile auslagern und dynamisch nachladen. Sie werden also nicht mit einem Programm zusammengepackt, sondern von beliebig vielen Programmen vom Start geladen. Der Vorteil ist hier, dass der Code nur einmal im Speicher liegen muss und die einzelnen Programme damit kleiner werden. Dynamische Bibliotheken werden vom Betriebssystem während der Startphase eines Programms geladen, noch bevor die Startroutine (main) aufgerufen wird.

Dynamisches Linken

Prinzipiell können dynamische Bibliotheken auch gelinkt werden - müssen sie aber nicht.

Dynamisches Laden