DLL und Shared Libraries

 

Für viele Programmieraufgaben kann es sinnvoll sein, Teile des ausführbaren Codes in externe Bibliotheken (DLL, Lib) auszugliedern. Anwendungsgebiete sind Programmteile, die von mehreren Programmen benutzt werden oder auch jene, die ganz im Gegenteil nur selten gebraucht und daher nur bei Bedarf geladen werden. In der Amateurastronomie kann auch die Bereitstellung eines funktionierenden, getesteten und in sich abgeschlossenen Rechenkerns an andere Programmierer ein Motiv sein, eine externe Bibliothek zur Verfügung zu stellen. Dies bietet sich an, wenn man diesen Rechenblock als Black Box ansehen oder vielleicht auch den Quelltext nicht offenlegen möchte.

Die Programmierung einer DLL unter Windows ist gar nicht so kompliziert. Eine DLL besitzt eine Schnittstelle nach außen in Form von Funktionen, die durch ihre Namen und Aufrufparameter definiert werden. Ein Programm, welches die DLL benutzt, kann diese Funktionen verwenden und aufrufen.

Mit dem MS Visual Studio kann eine DLL direkt als eigenständiges DLL-Projekt angelegt werden. Eine exportierte DLL-Funktion kann darin unter Verwendung bestimmter reservierter Schlüsselwörter (__declspec, dllexport) erzeugt werden. Eine einzelne Funktion wird in der Programmiersprache C mit folgenden Anweisungen exportiert (Datei use_me.c):

#include <windows.h>
extern "C" __declspec(dllexport) int MyExportedFunction( double Param1, int Param2 )
{
    // tue etwas
    // ...
}

In der zugehörigen Header-Datei (use_me.h) steht dann die Deklaration der Funktion:

#ifndef __DLL_HEADER_H__
#define __DLL_HEADER_H__
extern "C" __declspec(dllimport) int MyExportedFunction( double Param1, int Param2 );  // wichtig: dllimport statt dllexport
#endif

Ein Programm, welches die Funktion der DLL verwenden möchte, könnte wie folgt aufgebaut sein:

#include <windows.h>
#include "use_me.h"
int main(void)
{
    int Resultat;
    Resultat = MyExportedFunction( 1.5, 4 );
}

Der Linker wird die beim Compilieren der DLL erzeugte Lib-Date benötigen. Ferner muss die DLL sich im gleichen Verzeichnis wie die ausführbare Datei (.exe) befinden.

Wichtig ist, dass die Schnittstelle der DLL gut dokumentiert ist: Welche Funktionen gibt es, was machen sie, welche Parameter übernehmen sie und wie müssen die Parameter beschaffen sein.

Quelltexte der Sprache C lassen sich aber auch auf anderen Plattformen compilieren. Quintissenz des Gedankens der externen Bibliotheken wäre es, den Programmbestandteil auch für eine andere Plattform wie Linux zur Verfügung zu stellen. Unter Linux heißen die externen Programmbibliotheken Shared Libraries (Dateiendung .so); eine Beschreibung findet sich unter www.cprogramming.com/tutorial/shared-libraries-linux-gcc.html.

Die Quelltexte für beide Plattformen sind grundsätzlich gleich. Der Unterschied besteht darin, dass unter Linux die Funktionspräfixe extern "C" __declspec(dllimport/dllexport) nicht benötigt werden. Dies gilt ebenso für das Einbinden der Datei windows.h. Die Spezifikation des resultierenden Programms als Shared Library wird vielmehr über einen Kommandozeilenparameter (z.B. "gcc -shared" beim GNU-Compiler GCC) vorgenommen. Dieser Kommandozeilenparameter wird zudem auch oft im Makefile des Programms verbuddelt. Plattformspezifische Compilierung kann am besten mit Präprozessoranweisungen (#ifdef, usw.) erfolgen, die abhängig von der Plattform verschiedene Pfade bei der Compilierung durchlaufen lassen. Mit den Präprozessoranweisungen ändert sich der Code wie folgt (use_me.c):

#if defined(_MSC_VER)
    #include <windows.h>
    #define DLL_EXPORT   extern "C" __declspec(dllexport)
#else
    #define DLL_EXPORT
#endif

DLL_EXPORT int MyExportedFunction( double Param1, int Param2 )
{
    // tue etwas
    // ...
}

Wenn dieser Code für Windows übersetzt wird, schlägt das Define für _MSC_VER zu und definiert die Schlüsselwörter "extern "C" __declspec(dllexport)". Für Linux ist das Define _MSC_VER nicht gesetzt und der Platzhalter DLL_EXPORT bleibt leer.

Die Headerdatei use_me.h ändert sich wie folgt:

#ifndef __DLL_HEADER_H__
#define __DLL_HEADER_H__
#if defined(_MSC_VER)
    #include <windows.h>
    #define DLL_IMPORT   extern "C" __declspec(dllimport)
#else
    #define DLL_IMPORT
#endif
DLL_IMPORT int MyExportedFunction( double Param1, int Param2 );  // wichtig: dllimport statt dllexport
#endif /* von __DLL_HEADER_H__ */

Im aufrufenden Programm ändert sich an dieser Stelle nichts.

Wenn man bestimmte Rechenroutinen als dynamische Bibliothek zur Verfügung stellen möchte, kann es also eine sinnvolle Übung sein, sie durch diese paar Handgriffe gleich für eine weitere Plattform compilierbar zu gestalten.