DLLs dynamisch laden (Part 1)
Teil 2
Was euch erwartet
In diesem Tutorial zeige ich euch wie man eine DLL
und dann Funktionen dynamisch läd ( Also ohne .lib-include ).
Im zweiten Teil seht ihr dann wie man ganze Klassen dynamisch
laden kann
.
Dieses Tutorial ist angelehnt an das von DragonFlame,
welches ihr
HIER findet. Ich werde also nicht nochmal alles erklären,
was man seinem Tut bereits entnehmen kann !
Was bedeutet dynamisches Laden und was bringt mir das ?
In DragonFlame´s Tut wird gezeigt wie man Dll "statisch" einbinden
kann. Wenn man das Programm startet erwartet es eine bestimmte DLL.
Außerdem muss man den Header der DLL includiern, um sie zu benutzen.
Das ist beim dynamischen Laden nicht der Fall. Natürlich muss die DLL
spätestens dann vorhanden sein, wenn das Programm sie benutzen will,
jedoch würde das Programm erstmal ohne sie starten.
Das kann natürlich extrem praktisch sein. Wenn man z.B. ein Mal-Programm
macht kann man es ganz einfach erweitern indem man eine Liste der
vorhandenen DLLs (hier Plugins) hat, und dann das Läd was man Brauch.
Nun kann der Benutzer sich weitere DLLs runterladen, in die Liste eintragen
und benutzen ohne das eine neue .exe-Datei benutzt werden muss.
Los gehts !!!
Als erstes werden wir eine Dll (Name: "DieDll") erstellen, die dann von unserem Programm
geladen wird. Sie soll lediglig eine Message-Box öffnen ( Ratet mal wo ich das her hab
)
Der Include
Das Erste was wir machen ist ein Include:
|
C-/C++-Quelltext
|
1
|
#include <windows.h>
|
Ich denke, das ist klar.
Exportieren
Um uns das Leben zu vereinfachen kommt als nächstes eine
kleine Definition, die den Export erleichtert.
|
C-/C++-Quelltext
|
1
|
#define TUTORIAL_API __declspec(dllexport)
|
Da wir diese DLL nur Exportieren wollen,
brauchen wir __declspec(dllimport) nicht.
C - Style
In C++ können wir von jeder Funktion mehrere Versionen schreiben,
die alle unterschiedliche Parameter haben. Wenn wir nun eine Funktion
laden wollen weiss das Programm ja gar nicht welche Version es Laden soll.
Deshalb machen wir einen
extern "C" - Block um unsere Funktionen
die das C++-Feature "Überladen" ausschaltet.
|
C-/C++-Quelltext
|
1
2
3
|
#ifdef __cplusplus // Überprüfen ob mit C++ compiled wird
extern "C" { // Wenn Ja, dann ist es ab hier nur noch C
#endif
|
__cplusplus ist definiert, wenn man C++ benutzt ( was wohl bei uns der Fall ist).
DllMain
Das ist auch altbekanntes Zeug:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD dwReasonForCall,
LPVOID pReserved)
{
switch(dwReasonForCall)
{
case DLL_PROCESS_ATTACH:
//Der Referenzzähler wird um eins erhöht
break;
case DLL_PROCESS_DETACH:
//Der Referenzzähler wird um eins reduziert
break;
}
return TRUE;
}
|
Unsere Funktion
Hier unsere Funktion ( Relativ kurz gehalten
)
|
C-/C++-Quelltext
|
1
2
3
4
|
void TUTORIAL_API Message(void)
{
MessageBox(NULL, TEXT("Nachricht"), TEXT("Nachricht"), MB_OK);
}
|
C - Das war mal
So nun sind wir fast am Ende unserer Dll. Was noch fehlt, ist
den C-Block zu beenden.
|
C-/C++-Quelltext
|
1
2
3
|
#ifdef __cplusplus
} // extren "c"
#endif
|
So das wars. Und es geht spannend weiter mit dem Thema:
Die Lade-Funktion
Naja bis jetzt war noch nicht viel neues dar, was sich jetzt ändert.
Nun erstellen wir ein neues Projekt. Dieses soll unsere Dll Laden,
die Funktion "Message" suchen, und ausführen.
Mal wieder ein Include
Immer das gleiche ... Windows muss überall mit drinhängen
Wir includieren wie immer "Windows.h"
|
C-/C++-Quelltext
|
1
|
#include <windows.h>
|
WinMain
So hier wird Unser Programm einsteigen:
|
C-/C++-Quelltext
|
1
2
3
4
5
|
int WINAPI WinMain(HINSTANCE Instance,
HINSTANCE PreviousInstance,
char* CommandLine,
int ShowCommand)
{
|
Auch hier sollten noch keine Fragen aufkommen.
Unsere Variablen
Am anfang von WinMain werden wir ein Paar Variablen anlegen:
|
C-/C++-Quelltext
|
1
2
|
HMODULE hDLL; // Dies ist die Instance der Dll, die wir laden wollen
DllFunction fMessage; // Das ist die Nachrichtenfunktion die wir später aufrufen können
|
hDLL ist eine Art Instance unserer Dll. Das ist sozusagen unser
Tor zur ihr.
fMessage ist ein Zeiger auf unsere spätere Funktion und wird gleich noch erklärt.
Die Dll laden
Als erstes müssen wir nun die Dll-Datei laden, was mit
HMODULE LoadLibrary( lpLibFileName )
geht.
lpLibFileName ist jenachdem wie man compiled ( UNICODE ? Ja : Nein )
entweder LPCWSTR oder LPCSTR ( Wir benutzen das TEXT()-Makro um dynamisch zu bleiben).
LoadLibrary erwartet also den Datei-Namen der DLL, läd diese und
gibt eine Instance zurück. Wenn etwas schief ging ist sie NULL:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
|
hDLL = LoadLibrary( TEXT("DieDll.dll"));
if(hDLL == NULL)
{
// Fehler
MessageBox(NULL, TEXT("Die Datei DieDll.dll konnte nicht geladen werden!"),
TEXT("Fehler"), MB_OK | MB_ICONEXCLAMATION);
return 1;
}
|
Wenn hier alles richtig lief enthält unsere
hDLL jetzt etwas und es kann direkt mit dem Laden der Funktion weitergehen.
Die Message-Funktion
Jetzt kommt der schwierigste Teil. Doch bevor wir weitermachen mit dem
eigendlichen Laden der Funktion, will ich noch einmal auf
fMessage zurückkommen.
fMessage ist vom Type "DllFunction" die nicht in den
Tiefen irgendwelcher Header-Datein sitzt, sondern von mir "ge-typedef-t" wurde.
Das sieht wie folgt aus:
|
C-/C++-Quelltext
|
1
|
typedef void (CALLBACK* DllFunction)(void);
|
Sieht zwar verwirrend aus ist es aber nicht. "DllFunction" stellt lediglich einen
Zeiger auf eine Funktion dar, die so aussehen sollte:
|
C-/C++-Quelltext
|
1
|
void CALLBACK* /*Name der Funktion*/(void)
|
Also wie:
|
C-/C++-Quelltext
|
1
|
void TUTORIAL_API Message(void)
|
CALLBACK ist einfach nur _stdcall und zeigt wie die Funktion aufgerufen
werden soll.
Dieser Typedef nimmt uns einige Arbeit ab, denn wir könne direkt einen
Zeiger erstellen. Anders müssten wir so arbeiten:
|
C-/C++-Quelltext
|
1
|
void (CALLBACK* fMessage)(void);
|
Wobei unsere Version wohl übersichtlicher ist.
Aber nun wollen wir endlich die Funktion laden was wie follgt aussieht:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
|
fMessage = (DllFunction)(GetProcAddress(hDLL,"Message"));
if(fMessage == NULL)
{
// Fehler
MessageBox(NULL, TEXT("Die Funktion \"Message\" wurde in der DLL-Datei nicht gefunden !"),
TEXT("Fehler"), MB_OK | MB_ICONEXCLAMATION);
}else{ // Hier geht es dann gleich weiter
|
Ok Ok ganz langsam... Mit
FARPROC GetProcAddress( HMODULE hModule, LPCSTR lpProcName)
bekommen wir einen Zeiger auf unsere Funktion zurück (oder auch nicht).
hModule ist hierbei die DllInstance und
lpProcName der Name der Funktion.
Was hier auffält ist, das es keine Unicode-Ausgabe gibt !
Da der zurückgegebene Zeiger aber vom Type FARPROC ist müssen wir ihn noch
konvertieren. Auch hier macht sich unser Typdef wieder nützlich, denn
ohne ihn würde es so aussehen:
|
C-/C++-Quelltext
|
1
|
fMessage = (void (CALLBACK*)(void))(GetProcAddress(hDLL,"Message"));
|
Hier geht es ja noch, aber bei längeren Funktions-Köpfen sind Fehler
vorprogrammiert.
Danach wird einfach noch eine Fehlerabfrage gemacht, denn im Fall eines
Fehlers ist fMessage NULL.
Achtung: Bei dieser Fehlermeldung muss darauf geachtet werden, dass
die Library noch geschlossen werden muss. (Hier wird einfach ein
else benutzt.)
Endlich - Der Aufruf
Wenn alles glad lief, können wir nun die eigendliche Funktion aufrufen,
was wieder sehr einfach ausfällt:
|
C-/C++-Quelltext
|
1
2
3
|
}else{
fMessage();
}
|
Ohhh... wer hätte das gedacht
Tschüss Dll :cry:
Nun heisst es abschiednehmen und die DLL aus ihrem Dienst entlassen,
außerdem beenden wir das Programm:
|
C-/C++-Quelltext
|
1
2
3
|
FreeLibrary(hDLL);
return 0
}
|
Und das wars auch schon.
Ich denke ich brauche nicht zu erklären das
FreeLibrary( HMODULE hLibModule)
eine Instance unserer Dll haben will.
Weitere Aussichten
So ihr wisst jetzt wie man DLLs dynamisch läd. Es ist praktisch,
aber sehr aufwendig. In den meisten fällen reicht es also die
Dll statisch einzubinden ( Was man dann auch machen sollte).
In dem 2. Teil ( der wahrscheinlich bald rauskommt) zeige ich dann
wie man Klassen in DLLs verpackt, was super praktisch ist.
Schlusswort
So das wars erstmal. Ich hoffe es hat euch gefallen, war gut zu lesen und nicht zu langweilig.
Wenn ihr Fehler entdeckt postet sie bitte (Wahrscheinlich sehr viele im Bereich Rechtschreibung
).
Danke
Und nun noch die beiden Dateien:
DLLDynamischLaden.zip