Was sind DLL-Dateien und wie erstellt man diese?

Autor: 'Kriz'

Hallo, das hier ist mein erstes allgemeines DHCC Tutorial und ich möchte euch gerne mal zeigen, wie ihr mit VisualC++ eure eigene, lauffähige DLL erstellen könnt. Ich habe dieses Tutorial daher in mehrere Kapitel eingeteilt, damit meine Ausführungen nicht zu unübersichtlich werden:

  • Einführung in DLLs
  • Unterschiede zu statischen Libraries
  • C oder C++ für eine DLL verwenden?
  • Importierung und Exportierung innerhalb von DLLs
  • Eine DLL mit dem VisualC++ 6.0 Assistenten erstellen
  • Eine DLL in C geschrieben
  • Eine DLL in C++ geschrieben
  • Klassen in DLLs schreiben (C++ DLLs)
  • Einzelne Klassenmethoden in DLLs freigeben (C++ DLLs)
  • Nachbemerkungen
  • Tipps und Tricks

Einführung in DLLs

Der Begriff DLL ist eine Abkürzung für Dynamic-Link Library und bedeutet in Deutsch soviel wie dynamisch verlinkbare Bibliothek. Das Konzept der DLL ist eine konsequente Weiterentwicklung der in C/C++ üblichen und bekannten Library (Bibliothek), die statisch in Programme verlinkt wird.

Das Konzept, wie sie in einer DLL vorkommt, gibt es in ähnlicher Form auch auf *NIX/Linux Systemen. Dort spricht man dann von SO (Shared Object, geteiltes Objekt). Diese Bezeichnung trifft den Kern der Sache eigentlich schon näher als die Ausschreibung des DLL-Kürzels, denn es bezeichnet sehr deutlich die Funktionalität einer DLL zur Laufzeit eines Programms.

Eine DLL (.dll) beinhaltet genau wie eine Library Funktionen, Konstanten, Variablen usw. Diese Dinge werden in der Regel einmal kompiliert, stehen aber dann jedem Programm zur Verfügung, die sich des Inhalts der DLL bedienen möchte. Im Klartext bedeutet das, daß beliebig viele Programme zur Laufzeit Zugriff auf den Inhalt einer DLL haben, sofern diese Programme die Headerdatei(en) der DLL miteingebunden haben.

Von Haus aus können so ziemlich alle höheren Programmiersprachen unter Windows sowohl eigene DLLs erzeugen als auch darauf zugreifen (C, C++, Object(ive) Pascal, usw.). Dabei ist interessant zu wissen, daß die innere Struktur einer DLL anscheinend immer gleich nach einem bestimmten Muster erstellt wird. So ist es ohne weiteres möglich, daß unter Delphi erstellte ObjPascal-Windowsprogramme den Inhalt einer unter VisualC++ erstellten DLL importieren können und umgekehrt genauso. Allerdings - um es gleich vorwegzunehmen - ist dieser Prozeß nicht sehr angenehm. Wie bereits oben erwähnt muß ein Programm, welches auf den Inhalt einer DLL zugreifen möchte, auch die passenden Headerdatei(en) der DLL eingebunden haben. Da es in ObjPascal sowas wie „Headerdateien“ nicht gibt, werden dort alle DLL Import- und Exportdefinitionen direkt in den Code geschrieben, während in C/C++ sowas durchaus in den erwähnten Headerdateien passiert. Die Portierung einer ObjPascal-DLL-Schnittstelle nach C/C++ und umgekehrt ist daher abhängig vom Wissensstand der betreffenden Person. Trivial ist sowas nicht, daher sollte man bei solchen Vorhaben schon beiderseitig Ahnung von ObjPascal und C/C++ haben.

Ich selber kann zum Beispiel so gut wie keinen Brocken Pascal mehr und von ObjPascal habe ich bisher nur Beispiele im Internet gesehen. Die Syntax und Grammatik von ObjPascal (insbesondere von Delphi) ist mir daher ein Rätsel, weshalb ich zu DLL Fragen in diesem Bereich keinerlei Aussagen machen kann (und werde).

Welchen Sinn haben DLLs nun? Zum einen kann man diese Frage nach streng softwaretechnischen Aspekten betrachten, zum anderen aber auch nur aus rein ästhetischen Gründen. DLLs sparen viel Zeit und Geld, wenn sie (einmal erzeugt) allen anderen Programmierern zur Verfügung stehen. Ebenso ist es eine elegante (wenn auch etwas unfaire) Art der Codeversteckung. Der Programmierer bekommt nur die Definitionen zu Gesicht, während die Implementation verborgen in der DLL schlummert. Und darin liegt auch die Krux! Verändert ein Programmierer auch nur eine Kleinigkeit in der Definition, so wird der Zugriff auf die DLL unmöglich gemacht. Da in C/C++ alle DLL-Definitionen in einer Headerdatei stehen müssen, ist es eigentlich immer angebracht, dort als erstes einen fetten Hinweis als Kommentar zu schreiben: BITTE NICHTS VERÄNDERN UM FEHLER ZU VERMEIDEN!

DLLs haben darüberhinaus den Vorteil, daß der Zugriff von außerhalb zur Laufzeit passiert. Das bedeutet nichts anderes, als daß jeder Funktionsaufruf aus einem Programm heraus an die betreffende DLL in quasi-Echtzeit geschieht. Dabei spielt es außerdem keine Rolle, ob der Zugriff statisch erfolgt (als fest vordefinierter Bestandteil des Programms, sprich zur Kompilierungszeit bereits festgehalten) oder dynamisch (sprich zur Laufzeit des Programms)! Es werden immer nur die Bestandteile einer DLL dynamisch zur Verfügung gestellt, die das Programm im Moment auch benötigt. Dadurch werden die Programme kompakter und flexibler, da sich der Rest ja in der DLL befindet.

Der kleine Nachteil beim Einsatz von DLLs ist der „längere“ Zeitraum beim Aufrufen von DLL-Funktionen. Da alles zur Laufzeit geschieht, braucht der Computer etwas länger, ehe das Programm die gewünschten Daten erhält und weiterarbeiten kann. Da dieser Zugriff aber auf modernen Rechnern so schnell vor sich geht, kann man den Verlust an Rechenzeit durchaus mehr als vetretbar einstufen (solange es sich um Windowsprogramme handelt, die nicht wunder-was für Echtzeitsimulationen abhandeln müssen)…

Unterschiede zu statischen Libraries

Statische Libraries (.lib) beinhalten genauso wie DLLs Funktionen, Konstanten, Variablen usw. Der große Unterschied zu einer DLL liegt nur in der Art und Weise der Verlinkung beim Kompilieren eines Programms. Libraries werden komplett mit Haut und Haaren pro Quelltextmodul verlinkt. Ich sage ausdrücklich „pro Quelltextmodul“, da man sonst der Annahme nachgehen könnte, daß Libraries nur einmal pro Programm verlinkt werden, was aber definitiv nicht der Fall ist! Tatsächlich sieht es so aus: Wenn ein Programm aus sagen wir 100 einzelnen Quelltextmodulen (ein Pärchen aus einer Headerdatei + der passenden Quelltextdatei) besteht und jedes dieser Module eine Library einbindet, dann wird pro Modul die komplette Library verlinkt! Macht also summa summarum genau 100 mal ein- und diesselbe Library in nur einem Programm! Ich hoffe, ihr könnt euch vorstellen, wie groß dieses Programm wird, wenn es komplett auskompiliert wird (selbst wenn man es als „platzsparende“ Release-Version kompiliert). Zudem wird wie gesagt eine Library komplett verlinkt, also auch alle die Codeteile der Library, die man vielleicht überhaupt nicht benötigt. Im Endeffekt hat man dann meistens mehr ungenutzt verlinkten Code pro Modul als eigentlich nötig war. Außerdem kann nur das eine Programm auf die Funktionen der Library zugreifen, alle anderen Programme müssten erst mit der Library kompiliert werden.

DLLs dagegen werden nicht in das Programm verlinkt, sondern nur die Aufrufe (calls). Damit schlagen wir gleich 4 Fliegen mit einer Klappe:

  • Nicht ein Fitzel Implementierungscode aus der DLL ist in unserem Programm vorhanden.
  • Es wird nur derjenige Code an das Programm aus der DLL geliefert, den wir auch zum Zeitpunkt der Abarbeitung benötigen.
  • Alle Module unseres Programms können klein und kompakt gehalten werden sowie gleichzeitig den vollen Funktionsumfang der DLL nutzen.
  • Eine DLL kann unter Umständen beliebig viele Programme zur Laufzeit versorgen.

C oder C++ für eine DLL verwenden?

Die Win32 API basiert auf C, weshalb man ohne Probleme C-basierte DLLs erstellen kann. Da C in C++ als Untermenge definiert ist, ist auch eine C++-basierte DLL absolut kein Problem! Ja selbst eine gemischte DLL mit C und C++ Einflüssen ist machbar, wobei sowohl reines C als auch reines C++ gleichzeitig möglich gemacht werden kann!

Um gleich vorweg eine Frage aus der Welt zu schaffen: Die Win32 API ist die Schnittstelle zwischen DLLs und C/C++ Programmen. Das Benutzen der Win32 API bedeutet nicht, daß man DLLs nur für typische Windowsprogramme mit Fenstern und Buttons usw. benutzen kann! Die Win32 API ist das Fundament aller 32 Bit Windowsplattformen und der darauf basierenden Programme, weshalb man auch eine simple Win32 Konsolenanwendung mit den Inhalten einer DLL verbinden kann. Klartext: Eine DLL ist für alle Programme zugreifbar, die auf der Win32 API basieren.

Für welche der beiden Sprachen man sich beim Entwickeln einer DLL entscheidet, hängt davon ab, welche Programme den Inhalt der DLL benutzen sollen. Typische Windowsprogramme mit Fenstern usw. können sowohl C als auch C++ (oder beides) verlangen. Programmiert man streng nach der Win32 API, dann benutzt man C. Arbeitet man dagegen mit der MFC (Microsoft Foundation Classes), dann wird man den Mix aus C/C++ benutzen. Dagegen können reine C++ Konsolenanwendungen gänzlich auf C verzichten.

Ihr seht, daß bereits vor der Entwicklung einer DLL diverse Fragen abgehandelt werden müssen, damit möglichst viele Programme den Inhalt unserer DLL verwenden können. Und genau hier teilt sich das Spektrum in die zwei großen Teilgebiete der Windowsprogrammierung auf:

  • Die typische Windowsprogrammierung als Fensterapplikation
  • Die klassische Windowsprogrammierung als Konsolenanwendung

Ich werde hier Punkt b) behandeln, die klassische Variante also. Zu einem späteren Zeitpunkt werden ich in einem zweiten Teil dieses Tutorials auf die typische Variante eingehen, mit der man Fenster-DLLs erstellen kann.

Nun gut, wenn man jetzt eine DLL nur für den Eigengebrauch programmieren möchte, dann weiß man in der Regel bereits, für welche Sprache man programmiert (C oder C++). Dementsprechend sollte man auch das innere Design der DLL auslegen. Möchte man dagegen seine DLL der Welt zur Verfügung stellen, dann sollte man den Mix aus C und C++ bereitstellen, auch wenn er unter Umständen doppelt soviel Aufwand bedeuten könnte. Öffentliche DLLs, die sich nur auf ein Sprachkonzept spezialisieren, sind verpönt und zeugen nicht gerade von Professionalität!

Ich werde schrittweise beide Sprachen anreissen und später ein Gesamtkonzept vorstellen.

Importierung und Exportierung innerhalb von DLLs

Achtung!

Die später folgenden Codebeispiele behandeln ausschließlich das EXPORTIEREN von DLL Daten, nicht das Importieren!

Alle Daten in einer DLL werden normalerweise exportiert, d.h. nach außen hin verfügbar gemacht. Nur so können andere Programme auf den Inhalt einer DLL zugreifen. Aber genauso kann eine DLL aus anderen DLLs Daten importieren. Die dafür notwendigen Sprachkonstrukte werden wie gesagt von der Win32 API zu Verfügung gestellt. Dazu haben die Entwickler von VisualC++ 6.0 die dafür vorgesehenen Befehle und Anweisungen der Win32 API als Schlüsselwörter realisiert, d.h. sie werden im Editor von VisualC++ 6.0 blau dargestellt. Diese Schlüsselwörter sind keine Standardschlüsselwörter von C/C++, d.h. andere Compiler wie z.B. GCC werden mit diesen Schlüsselwörtern nichts anfangen können!

Benötigt wird in der Regel immer die sogenannte Deklarierungsspezifikation gefolgt von der Art der Deklarierung. Die Deklarierungsspezifikation lautet:

__declspec()

Die Art der Deklarierung wird in die Klammern geschrieben. Je nachdem ob wir etwas importieren oder exportieren möchten, lautet die Deklarierung:

dllimport

oder

dllexport

Die vollständige Syntax einer Importierungs- bzw. Exportierungsanweisung lautet daher:

__declspec(dllimport)__declspec(dllexport)

Das war der ganze Zauber auch schon, mehr gibt es nicht zu wissen… Nein, natürlich muß man schon noch einiges wissen, wie man mit diesen Befehlen umgeht =)

Da diese beiden Anweisungen fundamentaler Bestandteil von VisualC++ 6.0 sind, muß man weder eine spezielle Headerdatei einbinden noch sonstwas machen. Einfach hinschreiben, der Compiler versteht das automatisch.

Nun, fragt sich nur, was man als export und was als import deklarieren soll. Dazu folgende Faustregeln:

Alles, was aus der DLL rausgehen soll, wird als export deklariert
Alles, was in die DLL reingehen soll, wird als import deklariert.


Im Grunde genommen wird daher alles, was andere Programme verwenden sollen, als export deklariert. Dagegen kann es ab und an mal vorkommen, daß man aus anderen DLLs etwas benötigt, was man aber nicht nochmals implementieren möchte. Diese Dinge werden dann in unserer DLL als import gekennzeichnet.

Nun gut, genug gelabert über die Theorie. Kommen wir zu den handfesten Dingen des Lebens:

Eine DLL mit dem VisualC++ 6.0 Assistenten erstellen

Am einfachsten erstellt man eine DLL mit dem Assistenten. Dann brauch man nicht die speziellen Compilerschalter manuell einfügen. Dazu macht einfach folgendes:

  1. Klickt auf die Menüpunkte DATEI &raquo oder auf das Toolbar-Symbol
  2. Wählt nun im Projekt-Dialogfeld den Projekttyp Win32 Dynamic-Link Library aus
  3. Gebt einen Namen für das Projekt an, z.B. dlltest
  4. Klickt abschließend auf OK
  5. Wählt nun im Dialog die Option Leere DLL erstellen aus
  6. Klickt dann auf Fertigstellen
  7. Der Assistent wird dann einen neuen Arbeitsbereich mit dem Projekt erstellen. Jetzt fehlen uns nur noch eine Headerdatei und die dazu passende Quelltextdatei: Klickt auf die Menüpunkte PROJEKT → DEM PROJEKT HINZUFÜGEN → NEU…
  8. Wählt nun im Datei-Dialogfeld den Dateityp C/C++-Header-Datei aus
  9. Gebt als Dateinamen sowas wie z.B. dlltest ein
  10. Klickt auf OK

Nun haben wir schonmal das Grundgerüst für unsere DLL. Es fehlt noch die passende Quelltextdatei, die aber davon abhängt, ob man eine reine C-Datei (.c) oder eine C/C++-Datei (.cpp) haben möchte. Dazu sehen wir uns die nächsten Kapitel an:

Bemerkung:

Alle C/C++ Schlüsselwörter sind kursiv gedruckt.

Eine DLL in C geschrieben

In diesem Abschnitt schreiben wir eine reine C-DLL. Dazu benötigen wir eine reine C-Quelltextdatei:

  1. Klickt auf die Menüpunkte PROJEKT » DEM PROJEKT HINZUFÜGEN »
  2. Wählt nun im Datei-Dialogfeld den Dateityp C++-Quellcodedatei aus
  3. Gebt als Dateinamen sowas wie z.B. dlltest.c ein. Wichtig ist die explizite Dateiendung .c, da VisualC++ ansonsten automatisch die Datei mit der Endung .cpp versieht!
  4. Klickt auf OK

Als erstes Wechseln wir zur Headerdatei. Als Einstiegstest schreiben wir eine Struktur und zwei Funktionen, die etwas mit der Struktur machen. Denkt dran, daß es sich in diesem Kapitel um reines C handelt! Schreibt also folgenden Code rein:

/* Headerdatei "dlltest.h" --- Reines C */
#if!defined DLLTEST_H
#define DLLTEST_H
// Struktur deklarieren
__declspec(dllexport) struct STRUKTUR
{
    int wert;
};
// Funktion #1 deklarieren
__declspec(dllexport) int Get(struct STRUKTUR s);
// Funktion #2 deklarieren
__declspec(dllexport) void Set(struct STRUKTUR* ptr_s, int wert);
#endif // DLLTEST_H

Wie ihr sehen könnt, steht vor jeder Deklaration das magische Pärchen _declspec(dllexport). Damit liefert das Programm einen Hinweis an den Compiler, daß diese Codefragmente exportierbar sein sollen. Kommen wir nun zur Implementierung des Codes in der Quelltextdatei:

/* Quelltextdatei "dlltest.c" --- Reines C */
// Headerdatei einbinden
#include "dlltest.h"
// Funktion #1 implementieren
__declspec(dllexport) int Get(struct STRUKTUR s)
{
    return s.wert;
}
// Funktion #2 implementieren
__declspec(dllexport) void Set(struct STRUKTUR* ptr_s, int wert)
{
    if(ptr_s) ptr_s->wert = wert;
}

Auch hier muß vor jeder Implementierung das Pärchen _declspec(dllexport) stehen, damit der Compiler genau weiß, was er zu tun hat!

Wunderbar! Jetzt könnt ihr die DLL kompilieren lassen, indem ihr entweder F5 bzw. Strg+F5 drückt oder das passende Toolbar-Symbol anklickt. Die DLL wird dann erzeugt und VisualC++ fragt euch nach dem Pfad für ein passendes Remoteprogramm zum Austesten der DLL. Da wir ein solches Testprogramm (noch) nicht haben, klicken wir hier erstmal auf Abbrechen. Die DLL wurde aber trotzdem bereits erzeugt, also keine Panik =)

Soweit, sogut! Rein theoretisch wäre jetzt jedes Win32-C-Programm in der Lage, unsere neue DLL anzuzapfen und die darin enthaltene Struktur sowie die beiden Funktionen dynamisch zur Laufzeit zu laden und zu benutzen. Auf die Frage, wohin mit unserer DLL, damit auch jedes Programm sie nutzen kann, komme ich später zurück.

Als nächstes folgt die gleiche Implementierung als C++ Variante:

Eine DLL in C++ geschrieben

Hier benötigen wir eine reine C++-Quelltextdatei.

Falls ihr bereits das C-Beispiel ausgeführt habt, drückt bitte die Tastenkombination Alt+F7, damit ihr zu den Projekt-Einstellungen gelangt. Dort wählt ihr nun links im Dateibaum die Datei „dlltest.c“ aus und klickt auf die Option Datei von der Erstellung ausschließen. Danach einfach auf OK klicken und weiter geht's. Hintergrund ist der, daß der Compiler sonst zwei gleichnamige Quelltextdateien vorfinden würde und dann nicht weiß, welche er zum Kompilieren benutzen soll. Ok, los geht's:

  1. Klickt auf die Menüpunkte PROJEKT → DEM PROJEKT HINZUFÜGEN → NEU…
  2. Wählt nun im Datei-Dialogfeld den Dateityp C++-Quellcodedatei aus
  3. Gebt als Dateinamen sowas wie z.B. dlltest ein. Der Assistent hängt automatisch die Dateiendung .cpp an.
  4. Klickt auf OK
  5. Falls ihr die benötigte Headerdatei noch nicht erzeugt haben solltet, dann folgt bitte den Schritten in Kapitel 5 (Eine DLL mit dem VisualC++ 6.0 Assistenten erstellen). Achtet aber darauf, daß ihr keinen Code in die Headerdatei schreibt!


Als erstes Wechseln wir zur Headerdatei. Als Einstiegstest schreiben wir wieder eine Struktur und zwei Funktionen, die etwas mit der Struktur machen. Denkt dran, daß es sich in diesem Kapitel um reines C++ handelt! Schreibt also folgenden Code rein:

/* Headerdatei "dlltest.h" --- Reines C++ */
#ifndef DLLTEST_H
#define DLLTEST_H
// Struktur deklarieren
__declspec(dllexport) struct STRUKTUR
{
    int wert;
};
// Funktion #1 deklarieren
__declspec(dllexport) int Get(STRUKTUR s);
// Funktion #2 deklarieren
__declspec(dllexport) void Set(STRUKTUR& ref_s, int wert);
#endif // DLLTEST_H

Wie ihr sehen könnt, steht auch hier vor jeder Deklaration das magische Pärchen _declspec(dllexport). Damit liefert das Programm erneut den Hinweis an den Compiler, daß diese Codefragmente exportierbar sein sollen. Kommen wir nun zur Implementierung des Codes in der Quelltextdatei:

/* Quelltextdatei "dlltest.cpp" --- Reines C++ */
// Headerdatei einbinden
#include "dlltest.h"
// Funktion #1 implementieren
__declspec(dllexport) int Get(STRUKTUR s)
{
    return s.wert;
}
// Funktion #2 implementieren
__declspec(dllexport) void Set(STRUKTUR& ref_s, int wert)
{
    ref_s.wert = wert;
}

Auch hier muß wieder vor jeder Implementierung das Pärchen _declspec(dllexport) stehen, damit der Compiler genau weiß, was er zu tun hat!

Gut, jetzt könnt ihr die DLL kompilieren lassen, indem ihr entweder F5 bzw. Strg+F5 drückt oder das passende Toolbar-Symbol anklickt. Die DLL wird dann erzeugt und VisualC++ fragt euch nach dem Pfad für ein passendes Remoteprogramm zum Austesten der DLL. Da wir nachwievor ein solches Testprogramm (noch) nicht haben, klicken wir hier erstmal auf Abbrechen. Die DLL wurde aber erneut bereits erzeugt, also auch hier keine Panik aufkommen lassen =)

Rein theoretisch wäre jetzt jedes Win32-C++-Programm in der Lage, unsere neue DLL anzuzapfen und die darin enthaltene Struktur sowie die beiden Funktionen dynamisch zur Laufzeit zu laden und zu benutzen.

Nun wird es aber richtig interessant, denn schließlich bietet C++ Objektorientierung in Form der Klassen und Methoden an. Auch Klassen kann man exportieren bzw. importieren. Wie das funktioniert zeige ich euch jetzt:

Klassen in DLLs schreiben (C++ DLLs)

Der Weg zu einer DLL-Klasse ist sehr einfach und unterscheidet sich kaum von der DLLisierung (was ein Wort, hehe) normaler Funktionen! Der Unterschied besteht in der Syntax, mehr nicht. Schreiben wir also eine Klasse Test, die alles wichtige einer Klasse beinhaltet. Die Implementierung erfolgt in der Headerdatei:

// Quelltextdatei "dlltest.cpp"
#include "dlltest.h"
Test::Test() : m_iWert(0) {} // Konstruktor, initialisiert m_iWert in der Initialisierungsliste
Test::~Test() {} // Destruktor
int Test::Get() // Methode Get()
{
    return m_iWert;
}
void Test::Set(int wert) // Methode Set()
{
    m_iWert = wert;
}

Wow, hier sieht man sofort, daß das magische Pärchen _declspec(dllexport) überhaupt nicht mehr vorkommt! Und das stimmt. Eine Klasse, deren Prototyp komplett zum Export freigegeben worden ist, braucht keine explizite Kennzeichnung mehr bei der Implementierung. Und damir wäre dieses Kapitel auch schon beendet…

Einzelne Klassenmethoden in DLLs freigeben (C++ DLLs)

Hier geht es darum, daß man nicht die gesamte Klasse exportierbar macht, sondern nur ausgewählte Einzelmethoden der Klasse. Hä, was für einen Sinn macht ein solches Unterfangen? Diese Frage ist mehr als nur berechtigt!

Angenommen, man hat eine Basisklasse in der DLL, von der man in seinem eigenen Programm seine eigene Klasse ableiten möchte. Die Basisklasse hat eine Menge Methoden, die allerdings nur für die Basisklasse selber relevant sind. In einer abgeleiteten Klasse sollen diese Methoden nicht vorhanden sein. Daher exportiert man nur diejenigen Funktionen der Basisklasse, die auch relevant für eine abgeleitete Klassen sein sollen. Alle anderen Methoden sind der abgeleiteten Klasse unbekannt.

Dazu definiert man in der Headerdatei folgendes:

// Headerdatei "dlltest.h"
class Test
{
    int m_iWert; // Private Elementvariable
protected:
    void ChangeValue(void); // Geschützte Methode
public:
    __declspec(dllexport) Test(); // Freigegebener Konstruktor
    __declspec(dllexport) ~Test(); // Freigegebener Destruktor
    void Set(int wert); // Gesperrte Methode zum Setzen des Werts
    __declspec(dllexport) int Get(void); // Freigegebene Methode zum Holen des Werts
};

Das magische Pärchen _declspec(dllexport) wird bei solchen Klassen direkt vor die freizugebene Methode gesetzt. Hinter dem Schlüsselwort class dagegen wird _declspec(dllexport) nicht mehr gesetzt, da ja sonst die ganze Klasse freigegeben wäre.

So kann eine abgeleitete Klasse nur die Methode Set() erben, während Get() nicht bekannt ist. Andere in der DLL-Quelltextdatei erzeugten Objekte anderer Klassen, die auf diese Klasse in der DLL referenzieren, können dagegen ganz normal sowohl auf Get() als auch auf Set() zugreifen, da diese Objekte Bestandteil der DLL wären.

Wichtig ist nur, daß bei solchen methodenspezifischen Exportierungen auf jeden Fall auch ein eventuell vorhandener Konstruktor (und ggf. der Destruktor) mit _declspec(dllexport) gekennzeichnet werden sollte!

Nachbemerkungen

So, ich hoffe ihr versteht jetzt, wie man eigene C/C++ DLLs erzeugt und wie man sie einsetzt. Es gibt zum Thema DLL noch viel viel mehr zu erzählen (unter anderem das Importieren von externen Fremd-DLL-Daten), allerdings beschränken sich solche Dinge eher auf die typische Windowsprogrammierung mit Fensterapplikationen und weniger auf Konsolenanwendungen. Irgendwann werde ich diesen Teil über die Fensterapplikations-DLLs nachschieben.

Bevor ich nun mein Tutorial beende, möchte ich noch auf das ominöse Testprogramm für unsere DLLs eingehen. Im Prinzip muß dieses Testprogramm nur folgende Dinge beinhalten:

  • Es muß die Headerdatei der DLL implementieren
  • Es muß mit der DLL verlinkt werden (siehe unten)
  • Die DLL muß in einem der drei Lokationen platziert werden (siehe ebenfalls unten)

Eine DLL verlinken

Es gibt in VisualC++ 6.0 mehrere Möglichkeiten, wie man ein Programm mit einer DLL verlinkt. Zum einen fügt man ganz einfach die DLL zum Projekt hinzu oder man gibt den Pfad der DLL in den Projekt-Einstellungen unter der Rubrik DEBUG » Zusätzliche DLLs an. Am einfachsten ist jedoch die erste Methode.

Die drei Lokationen einer DLL

Eine DLL darf, um korrekt ausgeführt werden, in folgenden Lokationen platziert sein:

  • Im System-Verzeichnis von Windows (bzw. System32-Verzeichnis bei NT)
  • Im selben Verzeichnis wie das Programm
  • Als Pfadangabe in der AUTOEXEC.BAT

Windows wird nämlich folgende Suchwege vollziehen, um die DLL zu finden: Die Suche im System/System32-Verzeichnis

  • Die Suche im Programmverzeichnis
  • Die Suche über einen Pfad in der AUTOEXEC.BAT

Erst wenn keine dieser Möglichkeiten die DLL auffindbar machen, meldet Windows einen Fehler.

Das Testprogramm an sich arbeitet wie jedes andere Programm auch, d.h. es sind absolut keinerlei spezielle Dinge nötig, um auf die Bestandteile einer DLL zugreifen zu können. Folgendes Beispielprogramm zeigt es genauer (wir gehen mal davon aus, daß die benötigte DLL im selben Verzeichnis liegt wie das Programm und das es sich hierbei um die C*+ Version der Struktur-DLL handelt und nicht um die Klassen-DLL):

// Testprogramm "test.cpp"
#include <iostream.h>
#include "dlltest.h"
int main()
{
    STRUKTUR s;
    Set(s, 12345);
    cout << "Wert = " << Get(s) << endl;
    return 0;
}

Tipps und Tricks

Wer das magische Pärchen declspec(dllexport) nicht andauernd schreiben möchte, der kann ein solches Makro definieren: <code c> #define DLL_EXPORT declspec(dllexport) </code>

Anmerkung

Dieses Tutorial stammt aus der ehemaligen Sammlung des resourcecode.de und konnte dank der freundlichen Zustimmung des Autors in das thewall-Wiki übertragen werden.

Die Verwendung aller Dokumente einschließlich der Abbildungen ausschließlich zu nichtkommerziellen Zwecken. Verbreitung des Dokuments auf Speichermedien, (insbesondere auf CD-ROMs als Beilage zu Zeitschriften und Magazinen oder sog. "Mission-Packs" etc.) ist untersagt.
 
coding/cpp_windows/dll_dateien.txt · Zuletzt geändert: 2008/10/29 22:31 von TomMe