DLLs dynamisch laden (Part 2)
Teil 1
Hallo erstmal,
hat doch länger gedauert mich hier ran zu setzten, als ich geplant
hatte. Naja ... viel Spaß:
Jetzt bekommt das ganze Klasse
Wie die Überschrift indirekt zeigt, geht es dieses mal darum,
wie man Klassen aus Dlls läd. Volgende Kenntnisse werden
benötigt:
1) C++
2) Klassen in C++ ( auch Vererbung usw...)
3) Den ersten Teil dises Tutorials (
HIER noch einmal der Link)
4) Das Tutorial von DragenFlame ( Auch
HIER noch mal der Link)
5) "WinMain" und solche Sachen
So jetzt kanns los gehen:
Wie kann ich eine Klasse laden
Leider kann man keine Klassen direkt laden. Da sie aber eine
Art von Variablen sind kann man sie als return-Wert zurückliefern.
Das wars eigentlich auch schon. Einziger Nachteil: Es muss in beiden
Programmteilen( DLL + Programm ) die gleiche Klasse bekannt sein.
Problem dabei ist, das der Compiler im eigendlichen Programm
dann die Klassen-Funktions-Rümpfe nicht finden kann.
Lösung: Wir werden eine Eltern-Klasse machen, die nur "virtuele"
Memberfunktionen hat. Von dieser werden wir die eigentliche Klasse
ableiten. So kennt der Compiler zwar die Funktionen, sucht aber nicht
nach den Rümpfen. Also das erste ist Planung:
Die Planung
Nun müssen wir uns überlegen, was unsere Klasse können soll
Ich habe mich für zwei Funktionen, die jeweils eine MessageBox
öffnen, entschieden. Wir erstellen also eine Header-Datei ( bei mir heisst sie "Interface.h").Nun schreiben wir unsere Elternklasse:
Interface.h:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
|
#pragma once
class TheClassInterface
{
public:
virtual void SayVersion(void) = 0; // Gibt die Version aus
virtual void Message(void) = 0; // Schreibt einfach nur "Nachricht" xD
};
|
Hier sollte noch alles klar sein. Wenn ihr euch jetzt fragt,
was dieses "= 0" bedeutet:
Es sagt lediglich, das der Funktions-Rumpf nicht vorhanden ist.
In allen abgeleiteten Klassen,
muss diese Funktion vorhanden sein.
Man spricht hier von Abstraktion (
Weitere Info dazu).
Das war noch alles ganz einfach, machen wir mit der DLL weiter:
Die Dll
Ok jetzt kommt die eigentliche DLL. Am anfang ist noch nicht viel neues da:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
// Windows.h muss Inkludiert werden
#include <Windows.h>
// Header der Klasse
#include "Interface.h"
// Übersreiben der Klasse:
class TheClass : public TheClassInterface
{
public:
void SayVersion(void);
void Message(void);
};
#define TUTORIAL_API __declspec(dllexport)
// DLL-Hauptfunktion
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 beiden Test-Funktionen, die Einfach nur eine Nachricht ausgeben.
void TheClass::SayVersion(void)
{
MessageBox(NULL, TEXT("v1.0.1.2"), TEXT("Version"), MB_OK);
}
void TheClass::Message(void)
{
MessageBox(NULL, TEXT("Nachricht"), TEXT("Nachricht"), MB_OK);
}
|
So es sollte noch alles klar sein. Kommen wir jetzt zum interessanten Teil:
|
C-/C++-Quelltext
|
1
2
3
4
5
|
extern "C"
TUTORIAL_API TheClass* GetTheClass()
{
return new TheClass;
}
|
So... Eigentlich auch nichts neues. Wir haben hier unser geliebtes
extern "C" und TUTORIAL_API. Wie man unschwer erkennen kann,
handelt es sich hier um die Funktion die später geladen wird.
Sie Liefert einfach nur einen Zeiger auf eine Instance unserer Klasse (TheClass) zurück.
Jetzt können wir die Dll kompilen und siehe da, unsere DLL ist da
Das Hauptprogramm
Das Hauptprogramm enthält auch nicht viel neues. Ich werde euch erst
den Code zeigen, und dann noch mal auf die interessantesten/wichtigsten
Stellen eingehen:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
// Die üblichen Includs
#include <windows.h>
// Hier ist unser Klassen-Interface
#include "Interface.h"
// DllClassGetFunction ist ein typedef auf eine Funktion von dem type: TheClassInterface* _stdcall* /*Name*/ (void)
typedef TheClassInterface* (CALLBACK* DllClassGetFunction)(void);
// Die Hauptfunktion
int WINAPI WinMain(HINSTANCE Instance,
HINSTANCE PreviousInstance,
char* CommandLine,
int ShowCommand)
{
HMODULE hDLL; // Dies ist das Handel der Dll, die wir laden wollen
DllClassGetFunction fGet; // Das ist die Funktion, aus der wir unsere Klasse beziehen
TheClassInterface *cClass; // Enthällt später unsere Klasse
// Die DLL-Datei laden
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;
}
// So nun ist es soweit: Wir laden jetzt unsere DllFunktion aus der Dll
fGet = (DllClassGetFunction)(GetProcAddress(hDLL,"GetTheClass"));
if(fGet == NULL)
{
// Fehler
MessageBox(NULL, TEXT("Die Funktion \"GetTheClass\" wurde in der DLL-Datei nicht gefunden !"),
TEXT("Fehler"), MB_OK | MB_ICONEXCLAMATION);
}else{ // Wenn alles OK dann weiter:
// So jetzt die Klasse holen
cClass =( fGet() );
// Und Funktionen aufrufen
cClass->Message();
cClass->SayVersion();
// Jetzt die Klasse wieder Löschen
delete cClass;
}
// ... die dll freigeben...
FreeLibrary(hDLL);
// ... und Fertig !
return 0;
}
|
Interessant, ist, dass der Funktions-typedef "DllClassGetFunction" nicht
das gleiche wiedergibt wie "GetTheClass(void)". Hier geht das allerdings,
da das ganze erst zur Laufzeit bemerkt werden würde. Und außerdem,
können wir ja eine abgeleitete Klasse zu einem seiner Elternpaare
casten, was wir hier auch machen. So können wir aber auch nur die Funktionen
nutzen, die in "TheClassInterface" deklariert sind. Anders
macht der Compiler wieder Ärger
. Wenn wir weitere Memberfunktionen,
die nicht in "TheClassInterface" deklariert sind, in "TheClass" implementieren,
können wir diese nur aus Funktionen aufrufen, die in der Dll definiert sind.
Ich denke der Rest ist klar.
Schlusswort
So das wars schon. Eigentlich war es doch ganz einfach, oder ?
Mir kommt es so vor als wenn ich so wenig erklärt hätte ( Obwohl ich das alles sehr Selbsterklärend finde xD)
Solltet ihr trotzdem noch wichtige Fragen entdecken, die
noch nicht geklärt sind, postet diese bitte. Bitte postet auch Fehler.
Ich werde das Tutorial dann aktualisieren.
Feedback ist auch erwünscht
.