Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

Databyte

Alter Hase

  • »Databyte« ist der Autor dieses Themas

Beiträge: 1 040

Wohnort: Na zu Hause

Beruf: Student (KIT)

  • Private Nachricht senden

1

19.11.2007, 13:30

DLLs dynamisch laden (Part 2)

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++ :D
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 :D



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 :D :D .

Anonymous

unregistriert

2

19.11.2007, 16:41

Sehr schön wie viel Arbeit du da rein steckst . Du solltest um es nachher besser auffindbar zu machen immer die Teile gegeneinander verlinken. Ich habe dir das mal bei denn 2 Teilen die nun da sind gemacht (Oben unter der überschrift)

cu

Databyte

Alter Hase

  • »Databyte« ist der Autor dieses Themas

Beiträge: 1 040

Wohnort: Na zu Hause

Beruf: Student (KIT)

  • Private Nachricht senden

3

19.11.2007, 16:55

Zitat

Sehr schön wie viel Arbeit du da rein steckst .


Naja so viel Arbeit war es jetzt auch net... aber trotzdem Danke ;)

Zitat

Ich habe dir das mal bei denn 2 Teilen die nun da sind gemacht (Oben unter der überschrift)


Super ... danke, jetzt brauch ich es nicht mehr zu machen :D

DasBlub

Alter Hase

Beiträge: 802

Wohnort: Schweiz

Beruf: Programmierer

  • Private Nachricht senden

4

27.04.2008, 14:52

tolles tutorial ^^
hat mir viel geholfen.

evtl. sollte man das tutorial noch so erweitern, das es via eine template klasse geladen wird: http://www.flipcode.com/archives/Adding_…plication.shtml

5

27.04.2008, 16:49

Die Bezeichnungen sind manchmal ungluecklich. Z.B. erhaelt man mit

Quellcode

1
2
3
4
5
extern "C"
TUTORIAL_API TheClass* GetTheClass()
{
    return new TheClass;
}


nicht die Klasse, sondern eine Instanz (Objekt) dieser Klasse. Desweiteren fehlt ein Funktion zum Zerstoeren des Objekts, also eine delete-Aequivalent zu "GetTheClass". Sonst leckt der Speicher. :-) Bei deinem delete im Hauptprogramm kann viel schiefgehen. Fuer gewoehnlich werden dort Objekte vernichten, wo sie auch erstellt wurden.

David_pb

Community-Fossil

Beiträge: 3 886

Beruf: 3D Graphics Programmer

  • Private Nachricht senden

6

27.04.2008, 17:48

Zitat von »"knivil"«

Fuer gewoehnlich werden dort Objekte vernichten, wo sie auch erstellt wurden.


Für gewöhnlich ist die Richtlinie korrekt. Aber es muss nicht so sein und im allgemeinen Fall bringt das auch keine Probleme mit sich (solang der Nutzer sich an gegebene Kriterien hält).
@D13_Dreinig

DasBlub

Alter Hase

Beiträge: 802

Wohnort: Schweiz

Beruf: Programmierer

  • Private Nachricht senden

7

27.04.2008, 18:46

im normalfall wird die funktion ja auch so verwendet:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
//function etc. laden

...

// get new instance

TheClassInterface* myClass = GetTheClass();

// do stuff

myClass->foo(bar);

// remove pointer

delete myClass;
myClass = NULL;


dadurch wird die klasse wieder gelöscht

Das Gurke

Community-Fossil

Beiträge: 1 996

Wohnort: Pinneberg

Beruf: Schüler

  • Private Nachricht senden

8

27.04.2008, 19:53

Aber das *kann* knallen! Ich habe keine Ahnung was genau die Regeln beim allokieren und freigeben von Speicher im Zusammenhang mit DLLs sagen. Aber ich persönlich hatte schon einige male recht merkwürdige Abstürze, weil ich versucht habe im "Hauptprogramm" Speicher freizugeben, den ich in einer DLL allokiert habe.

DasBlub

Alter Hase

Beiträge: 802

Wohnort: Schweiz

Beruf: Programmierer

  • Private Nachricht senden

9

27.04.2008, 20:03

beim aufruf von

C-/C++-Quelltext

1
delete MyClass;

wird ja der konstruktor der klasse aufgerufen. DA musst du dann den speicher freigeben, den du in der dll allokiert hast.

Databyte

Alter Hase

  • »Databyte« ist der Autor dieses Themas

Beiträge: 1 040

Wohnort: Na zu Hause

Beruf: Student (KIT)

  • Private Nachricht senden

10

27.04.2008, 20:11

Also in dem code aus dem ich das Tut gemacht habe wurde der speicher
genauso freigegeben ( also einfach delete im Hauptprogramm )



@knivil: Du hast natürlich recht: es wird nur ein zeiger auf eine instance der
Klasse zurückgegeben

Werbeanzeige