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

10.11.2007, 16:24

DLLs dynamisch laden (Part 1)

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


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

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


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

Danke





Und nun noch die beiden Dateien:

DLLDynamischLaden.zip

Sheddex

unregistriert

2

10.11.2007, 16:56

Eigentlich sollen Tutorials in "2D/3D Spieleprogrammierung" erstellt werden.
Schreib' also am besten einen Mod bezüglich der Verschiebung an.

Zum Tutorial: Ich hab' das jetzt überflogen und finde es auf den ersten Blick gelungen. Außerdem muss ich mir jetzt nicht die Mühe machen, ein eigenes zu schreiben :)

Nox

Supermoderator

Beiträge: 5 272

Beruf: Student

  • Private Nachricht senden

3

10.11.2007, 17:02

Hmm ich bin der Meinung es kann ruhig schon hier stehen. Falls sich zeigen sollte, dass es doch viel Diskussionsbedarf gibt, werde ich es gerne verschieben/bereinigen.
PRO Lernkurs "Wie benutze ich eine Doku richtig"!
CONTRA lasst mal die anderen machen!
networklibbenc - Netzwerklibs im Vergleich | syncsys - Netzwerk lib (MMO-ready) | Schleichfahrt Remake | Firegalaxy | Sammelsurium rund um FPGA&Co.

David_pb

Community-Fossil

Beiträge: 3 886

Beruf: 3D Graphics Programmer

  • Private Nachricht senden

4

10.11.2007, 17:38

Re: DLLs dynamisch laden (Part 1)

Zitat von »"Databyte"«


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



Hä?? :shock:
@D13_Dreinig

BlackSnake

Community-Fossil

Beiträge: 1 549

Beruf: Student

  • Private Nachricht senden

5

10.11.2007, 18:05

bei c++ muss da immer extern "C" stehe. also, um die funktion herum, die exportiert werden sollen...

Databyte

Alter Hase

  • »Databyte« ist der Autor dieses Themas

Beiträge: 1 040

Wohnort: Na zu Hause

Beruf: Student (KIT)

  • Private Nachricht senden

6

10.11.2007, 22:34

Re: DLLs dynamisch laden (Part 1)

Zitat von »"David_pb"«

Zitat von »"Databyte"«


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



Hä?? :shock:


1. Sorry hab net richtig gelesen, dachte man muss es dahin posten wo es am besten passt
2.Also wenn __cplusplus nicht definiert ist, wird wahrscheinlich mit C compilet,
was bedeutet das man extern "c" nicht brauch.

David_pb

Community-Fossil

Beiträge: 3 886

Beruf: 3D Graphics Programmer

  • Private Nachricht senden

7

11.11.2007, 10:18

Zitat von »"BlackSnake"«

bei c++ muss da immer extern "C" stehe. also, um die funktion herum, die exportiert werden sollen...


Nö!

Zitat von »"databyte"«


2.Also wenn __cplusplus nicht definiert ist, wird wahrscheinlich mit C compilet,
was bedeutet das man extern "c" nicht brauch.


Das ist schon richtig. Aber was du schreibst wofür man extern "c" benötigt. Das ist falsch!

Siehe auch hier:

Zitat von »"databyte"«


Leider unterstützen DLL-Funktionen, die geladen werde sollen,
das C++-Feature des Überladens nicht, was bedeutet wir müssen
einen C-Block um unsere Funktion machen:
@D13_Dreinig

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

8

11.11.2007, 10:26

EDIT: *snipp*

David_pb

Community-Fossil

Beiträge: 3 886

Beruf: 3D Graphics Programmer

  • Private Nachricht senden

9

11.11.2007, 10:32

Zitat von »"dot"«

verrätst du uns auch, worauf du hinauswillst?


Ich will auf das name mangling hinaus, das durch extern "C" unterdrückt wird. Ich dachte das Databyte sich vielleicht nur ungenau ausdrückte und wollt da nur nachhaken. Aber von mir aus:

Das extern "C" unterdrückt das bei C++ übliche Name-Mangling. Das bedeutet das die exportierten Funktionen mit einem zusammengesetztem Namen bezeichnet werden (das kann je nach Compiler variieren). Leider ist das importieren einer solchen Funktion etwas "komplizierter", weil der Name vom Orginalnamen abweicht.
Allerdings erlaubt extern "C" auch C++ Features, d.h. die Sprache wird nicht gewechselt, lediglich das Name Mangling wird unterdrückt und die Funktionen werden mit ihrem tatsächlichen Namen exportiert.
@D13_Dreinig

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

10

11.11.2007, 10:42

jo. mein beitrag bezog sich auf eine frühere version von deinem beitrag weiter oben und dein beitrag jetzt wiederum auf eine frühere version von meinem ;)

jedenfalls stand damals nur das zitat von databyte da, mit nem "Nö" drunter oder so, und ich dachte, dass das keine sehr hilfreiche antwort ist. dann hab ich, nachdem ich gepostet hatte gesehen, dass du editiert und eine weitaus hilfreichere antwort geschrieben hast, was mich zu der ansicht kommen lies, dass mein beitrag nun nichtmehr angebracht sei. leider geht das löschen hier drinnen net, deswegen snipp...

naja, blöde verkettung von ereignissen^^

was das name mangling angeht: das ist noch dazu compilerspezifisch, was das dynamiche laden von funktionen mit solchen dekorierten namen imo wirklich grauslig macht.

Werbeanzeige