Eine DLL = Dynamic Link Library enthät Programmcode der aus dem eigentlichem Hauptprogramm (*.exe) ausgelagert ist. Dabei kann jede Art von Programmcode in eine DLL verlagert werden. Dazu gehören Funktionen, Klassen, Resourcen und Formulare. Durch die Auslagerung von Programmteilen in eine DLL stehen diese dann nicht nur einem Programm zu Verfügung sondern jedem Programm das diese Funktionen nutzen will.
Vorteil der Nutzung einer DLL ist nicht nur, dass der Programmcode verschiedenen Programmen zur Verfügung steht, sondern die DLL's bieten die Möglichkeit durch die Auslagerung mit den Resourcen des Rechners sparsamer umzugehen. Weiterhin kann ein Programm natürlich mehrere DLL's nutzen. Dadurch besteht die Möglichkeit je nach Umgebung z.B. DLL's zu nutzen, die alle Dialoge in einer Sprache enthalten (Lokalisierung).
Hauptgrund dürfte jedoch die Wiederverwendung des Programmcodes sein. Natürlich kann man Quelltext-Teile durch einbinden der einer Headerdatei ebenfalls wiederverwenden. Jedoch wird dadurch das Programm größer die Turn-Around Zeit länger usw. Allerdings sollten dann Änderungen an einer DLL gut geplant sein, damit Programme, die auf der älteren Version basieren ebenfalls mit den Änderungen zu recht kommen.
Um eine bestehnde DLL in einem Projekt zu verwenden gibt es zwei Methoden.
Bei der ersten Methode wird die DLL bei Start des Programmes mitgeladen und steht sofort zur Verfügung (statisches Laden einer DLL).
Bei der zweiten Methode wird die DLL erst dann geladen, wenn es tatsächlich erforderlich ist (dynamsiches Laden einer DLL).
Beide Methoden haben ihre Vor- und Nachteile. So ist der Umgang mit einer statischen DLL recht einfach, während man bei der dynamsichen DLL erst doch Adressen ermitteln muß. Jedoch ist die dynamsiche DLL natürlich resourcen schonender.
Um eine fremde DLL zu nutzen sind 3 Dinge vonnöten:
Eine LIB Datei kann nur Not mit dem Dienstprogramm IMPLIB im BIN-Verzeichnis des C++Builders aus einer DLL erzeugt werden.
Fertig
Es wird davon ausgegangen, dass es in der DLL mit dem Namen test.dll eine Funktion "Berechnen" gibt, in in einem eigenen Projekt genutzt werden soll. Zunächst ist die DLL zu laden. Dazu steht die WIN 32 API Funktion LoadLibrary zur Verfügung. Diese erwartet als Parameter den DLL-Namen und gibt eine Instanz der DLL zurück.
HINSTANCE hdll=LoadLibrary("test.dll");
Dann muß ein Funktionszeiger der Routine berechnen deklariert werden. Es wird davon ausgegangen, dass in der DLL die Funktion wie folgt angeleget wurde:
void _export __stdcall Berechnen(int summe);
Der Funktionszeiger sollte dann so deklariert werden:
void (__stdcall* Berechnen)(int);
Dem deklarierten Zeiger muß jetzt die Funktion aus der DLL zugeordnet werden. Hierzu muß die WIN 32 API Funktion GetProcAddress verwandt werden:
Berechnen=(void(__stdcall*)(int))GetProcAddress(hdll,"_Berechnen");
Nun kann die Funkiton genutzt werden. Ist die DLL nicht mehr vonnöten muß diese freigegeben werden:
FreeLibrary(hdll);
Auch in diesem Fall muß die DLL entweder im Verzeichnis der Applikation, im Windows/System/-Verzeichnis oder in einem Verzeichnis liegen, welches in der Umgebungsvariablen PATH enthalten ist.
Windows schau an folgenden Orten beim laden nach, ob es die DLL findet:
M.E. ist die DLL, sofern diese nicht tatsächlich von mehreren Programmen genutzt werden "im Verzeichis der EXE" am besten aufgehoben.
Es gibt zwei verschiende Arten von Funktionen / Klassen einer DLL. Solche die freigegeben sind und von allen Programmen genutzt werden können und solche die nur innerhalb DLL von dieser DLL genutzt werden können.
Aus diesem Grunde sin alle Funktionen / Klassen einer DLL die auch außerhalb der DLL genutzt werden soll mit dem Schlüsselwort __export zu versehen. Beispiel "class __export GFile" oder "char* __export GetGFileVersion();".
Innerhalb einer Anwendung sind diese Funktionen dann zu importieren und aus diesem Grunde mit dem Schlüsselwort __import zu versehen.
Mit dieser Methode kann bei der Erstellung einer DLL entschieden werden, welche Funktionen / Klassen für andere zugänglich sind.
Üblicherweise nutzt man zur Erstellung von Programmen Header-Dateien. Diese enthalten u.a.die Prototypen der Funktionen / Klassen. Nun
müßte man als Ersteller einer DLL zwei Header Dateien vorrätig haben. Die eine ist für die Erstellung der DLL notwendig. Dort sind alle
Funktionen / Klassen die exportiert werden soll mit __export versehen.
Die andere ist für die Anwendung die die DLL nutzen will. Dort sind alle
Funktionen / Klassen die exportiert werden soll mit __import versehen.
Um dieses Problem zu lösen gibt es die präprozessor Dirketive __decelspec(....). Es wird folgender Kopf in die Header-Datei aufgenommen:
#ifdef __DLL__ # define DLL_TYP __declspec(dllexport) #else # define DLL_TYP __declspec(dllimport) #endif
Hier wird anahnd des Symbols __DLL__ enschieden, ob die Header-Datei innerhalb der Erstellung einer DLL angewandt wird. Ist dies der Fall so wird das Macro "DLL_TYP __declspec(dllexport)" definiert. In anderen Fall das Macro "DLL_TYP __declspec(dllimport)". Nun "weiß" die Headerdatei in welchem Fall sie welches Macro nehmen sollen. Folglich sehen die Deklarationen der Funktionen dann so aus:
class DLL_TYP GFile
char* DLL_TYP GetGFileVersion();
Jetzt kann sowohl für die Compilierung der DLL als auch der Anwendung eine Header-Datei genutzt werden. Üblicher weise sollte ein Includewächter ebenfalls dabei sein, der dafür sorgt, dass die Headerdatei wenn sie in mehreren Modulen einer Anwendung genutzt wird tatsächlich nur einmal eingebunden wird . Im Beispiel der Klasse GFile könnte ein Includewächter wie folgt aussehen:
//Includewächter -> auf das #endif achten
#ifndef GFILEH
#define GFILEH
class DLL_TYP GFile
{
public:
......
private:
........
};
//Weitere Funktionen
double DLL_TYP round(double zahl,int stellen);
#endif
Damit die Funktionen auch von jeden Programm genutzt werden können, sollten sie ggf. mit extern "C"deklariert werden.
Manchmal ist es wichtig, wenn die DLL aufgerufen oder beendet wird bestimmte Routinen zur Speicherbelegung oder Speicherfreigabe o.a. auszuführen. Hierzu kann die Eintrittsfunktion der DLL genutzt werden. Diese sieht wie folgt aus:
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
return 1;
}
In der Variablen hinst wird das Instanzhandle der DLL geliefert und steht für Routinen zur Verfügung. Der Parameter reason gibt an, warum diese Eintrittsfunktion aufgerufen wurde:
| DLL_PROCESS_ATTACH | Die DLL wurde geladen |
| DLL_THREAD_ATTACH | Durch den zugehörige Prozeß wurde ein neuer Thread gestartet |
| DLL_THREAD_DETACH | Durch den zugehörige Prozeß wurde ein Thread beendet |
| DLL_PROCESS_DETACH | Die DLL wird freigegeben (z.B. durch Aufruf von FreeLibrary(hdll);) |
Der Parameter lpReserved:
Im C++Builder ist dazu nur unter "Projekt->Optionen->Linker" die Checkbox "Importbibliothek erzeugen" anzuhacken. Die Lib-Datei wird dann automatisch erzeugt.
Unter Projekt->Optionen->Verzeichnisse/Bedingungen" kann bei "BPI/LIB-Ausgabe" der Pfad eingestellt werden, unter dem die LIB-Datei abgelegt wird.
Zur Not kann auch aus einer DLL direkt mit dem Dienstprogramm IMLIB ein LIB-Datei erstellt werden.
Wie in einer ganz normalen Anwendung kann man Formulare in einer DLL erstellen. Es könne die Komponenten der VCL genutzt werden. Das die Header-Datei von Formularen vom C++Builder erzeugt werden, müssen die Klassen der Formulare allerdings exportiert werden. Dabei gelten die gleichen Regeln wie bei den Funktionen / Klassen. Üblicherweise schreibt der C++Builder folgendes:
class TForm1 : public TForm
dies wird in
class DLL_TYP TForm1 : public TForm
geändert.
Das weitere Vorgehen ist genau so wie bei den Funktionen / Klassen.
Die LIB-Datei und die Header-Datei müssen in das Projekt eingebunden werden. Von der Klasse TForm wird zunächst eine neue Instanz abgeleitet. Dann kann das Formular angezeigt werden. Wird es nicht mehr benötigt, ist der Speicher freizugeben.
TForm1 *test=new TForm1(); test->ShowModal; ..... delete test;
Eine DLL ist der ideale Ablageort für Resourcen. Sie stehen möglicherweise vielen Dateien zur Verfügung, machen die EXE nicht groß und können bei Bedarf geladen werden. Hier ist jetzt die Rede von einer DLL die NUR Resourcen enthält; keinen Programmcode.
Zunächst wird mit dem C++Builder ein neues DLL-Projekt eröffnet. Das war es auch schon mit der Programmierung in der DLL. Der Programmcode Zu dem Projekt wird normalerweise eine *.res Datei gleich mitangelegt. Mir ist es nicht gelungen, dort meine Resourcen unterzubringen. Offenbar wird diese Datei immer wieder neuerzeugt.
Um eine Resource in eine EXE oder DLL zu bringen ist eine *.res Datei vonnöten. Sofern nur einfache Bitmaps, Symbole oder Cursors in die Resource sollen, kann diese *.res Datei dirket mit dem Bildeditor von Borland erzeugt werden. Bei einer *.res Datei handelt es sich um eine vom Resoucrencompiler erzeugte Datei. Grundlage dieser datei ist eine *.rc Datei. Dabei handelt es sich um eine Textdatei, die die Resourcen oder Verweise auf Resourcen enthält.
Es wird also eine Textdatei mit einer Endung *.rc erzeugt. Diese Datei wird mit in das DLL Projekt aufgenommen. Danach kann die Datei im Editor bearbeitet werden. Das folgende Beispiel zeigt zwei Bitmaps die in die *.rc Datei aufgenommen werden. Dabei wird nicht das Bitmap selbst in die *.rc Datei aufgenommen (was auch möglich ist), sondern per Dateipfad auf die Bitmaps verwiesen:
BILD1 BITMAP "E:\pic1.bmp" BILD2 BITMAP "E:\pic2.bmp"
Die beiden Bitmaps bekommen einen Namen (BILD1, BILD2). Danach erfolgt die Klassifizierung um was für eine Art von Resource es sich handelt (BITMAP, SYMBOL, CURSOR, RCDATA), und dann der Pfad zur Bilddatei (*.bmp). Nun kann die *.rc Datei gespeichert werden.
Aus der Projektverwaltung wird nun die *.rc Datei ausgewählt und mit der rechten Maustaste "compilieren" ausgewählt.

Nunmehr erzeugt der Resourcecompiler eine *.res Datei. Danach kann die DLL erstellt werden. Diese enthält jetzt die Resourcen. Hierbei ist es nicht erforderlich eine *.lib Datei erzeugen zu lassen, da ja keine Funktionen oder Klasse exportiert werden.
Nun sollen ja die Resourcen auch in der EXE genutzt werden. Zunächst muss die DLL eingebunden werden. Da nichts aus der DLL exportiert wird, ist es in diesem Fall leichter, dass dynamische Laden vorzunehmen. Zunächst wird die DLL geladen. Dabei wird der Name der erzeugten DLL vorgegeben.
HINSTANCE hinstDLL=LoadLibrary("meine.dll");
hinstDLL muss nach erfolgreichem Laden <>0 sein. Jetzt kann ein Zugriff auf die gespeicherten Bitmaps erfolgen. Zunächst legt man ein TBitmap-Objekt an und lädt dann die Resource hinein
Graphics::TBitmap *mybitmap1=new Graphics::TBitmap(); mybitmap1->LoadFromResourceName((int)hinstDLL,"BILD1"); Graphics::TBitmap *mybitmap2=new Graphics::TBitmap(); mybitmap2->LoadFromResourceName((int)hinstDLL,"BILD2");
Nun ist die Resource im Bitmap-Objekt und kann genutzt werden.
Auch hier wird zunächt ein neues Projekt für DLL's erzeugt (oder das schon vohandene weiter genutzt). Auch wird wieder eine *.rc Datei erzeugt.
Bild RCDATA "g:\\Bild1.jpg"
Für JPG Bilder gibt es kein extra Resourcentyp. Aus diesem Grunde wird der allgemeine Klasse RCDATA gewählt. Aus diese *.rc Datei wird compiliert . Auch hier ist die Erstellung einer *.lib Datei nicht erforderlich.
Nun sollen ja die Resourcen auch in der EXE genutzt werden. Zunächst muss die DLL eingebunden werden. Da nichts aus der DLL exportiert wird, ist es in diesem Fall leichter, dass dynamische Laden vorzunehmen. Zunächst wird die DLL geladen. Dabei wird der Name der erzeugten DLL vorgegeben.
HINSTANCE hinstDLL=LoadLibrary("meine.dll");
hinstDLL muss nach erfolgreichem Laden <>0 sein. Jetzt kann ein Zugriff auf die gespeicherten Bitmaps erfolgen. Im Gegensatz zu dem Beispiel mit den BMPs gibt es jedoch keine spezielle Methode für den Zugriff auf RCDATA.
Um mit einem JPG zu arbeiten ist die
#include <jpeg.hpp>
einzubinden. Hier wird dann TResourceStream genutzt:
TResourceStream *rcStream=new TResourceStream((int)hinstDLL,"BILD","RT_RCDATA");
Nun liegt ein Stream auf die JPG-Resource vor.
TJPEGImage *jpg=new TJPEGImage(); Graphics::TBitmap *mybitmap=new Graphics::TBitmap(); jpg->LoadFromStream(rcStream); mybitmap->Assign(jpg);
Es wird eine neues TJPEGImage Objekt erzeugt und eine neue Bitmap. Dann wird dem TJPEGImage Objekt der Stream zu laden zugeteilt. Das geladene JPG-Bild wird dann dem Bitmap-Objekt zugeordnet und kann dann angezeigt werden.
Und aufräumen nicht vergessen:
delete jpg; delete mybitmap; FreeLibrary(hinstDLL);
Im übrigen sei auf die Hilfe im C++Builder verwiesen.