AFFICHER CET ARTICLE EN MODE PAGE ENTIERE
SOMMAIRE
4) Ecriture d'un programme utilisant notre DLL
Cet article vise à vous montrer
les possibilités offertes par les DLL WIN32, utilisées
par beaucoup d'applications, y compris WINDOWS lui-même.
Nous allons tout d'abord expliquer l'utilité de tels fichiers, puis nous
allons voir comment écrire nos propres DLL
en C.
Pour comprendre cet article, il est préférable de posséder
des bases en C.
Vous vous êtes peut-être déjà
demandé à quoi correspondaient tout ces fichiers *.dll présents
dans la plupart des programmes, et si abondants dans le dossier ‘c:\windows\system32’.
Ces fichiers ne contiennent en réalité aucun code exécutable,
mais seulement une liste de fonctions ‘exportées’ ; DLL
signifiant par ailleurs Dynamic Linked
Library (Bibliothèque Liée Dynamiquement),
en opposition aux bibliothèques classiques de fonctions qui sont qualifiées
de ‘statiques’.
Cela signifie que n’importe quel exécutable
peut accéder à ces fonctions et les utiliser, sans que la fonction
ne soit chargée en mémoire indépendamment pour chaque programme.
Ainsi, la mémoire est économisée, et si la même fonction
est utilisée par différents exécutables, il est avantageux
de l’écrire dans une DLL. Toutes les
fonctions que vous utilisez habituellement dans vos programmes, telles que strncpy()
ou printf() sont stockées
dans les DLL de WINDOWS, pour que la mémoire
ne soit pas surchargée par l’utilisation constante de ces fonctions.
Si vous voulez avoir la liste des fonctions exportées par une DLL,
vous pouvez par exemple les ouvrir avec W32Dasm et aller dans le menu
Functions | Exports
, vous pourrez ainsi voir à quoi sert chaque DLL. Il
existe aussi des outils spécialisés permettant de faire la même
chose, comme ‘Depends’, fourni avec VISUAL C++ 6.0 (et
peut-être les autres versions).
Bien, maintenant que vous savez en quoi consiste une DLL, il est temps de passer à la pratique, et d’en coder une nous-mêmes. Comme exemple, j’ai choisi une DLL simple, exportant une seule fonction permettant une vérification de mot de passe. Il s’agit d’un exemple simple, pas très utile en soi, mais qui me permettra d’illustrer mes explications.
Tout d’abord il faut savoir qu’une
DLL ne contient pas de fonction main()
comme les exécutables classiques, mais une fonction équivalent
qui lui est propre : DllMain().
Sa structure est la suivante, comme indiquée dans la documentation de microsoft :
BOOL
WINAPI DllMain( _ HINSTANCE hinstDLL, // handle to the DLL module _ DWORD fdwReason, ___// reason for calling function _ LPVOID lpvReserved _// reserved ); |
Cette fonction constitue donc la fonction principale d’une DLL, et est par conséquent indispensable à sa compilation. A l’image des arguments de la fonction main(), ce n’est pas à vous de donner une valeur aux trois arguments de DllMain() : ils servent à apporter des précisions sur les conditions d’appel de la DLL.
hinstDLL
est un handle vers la DLL en cours, c’est-à-dire
qu’il nous servira à désigner la DLL
dans certaines fonctions.
lpvReserved est
très peu utile, et vaut NULL
la plupart du temps.
L’argument le plus important est fdwReason,
qui nous indique pour quelle raison la fonction DllMain()
a été appelée.
Il peut prendre quatre valeurs différentes :
- DLL_PROCESS_ATTACH : La DLL
est chargée en mémoire suite à l’appel de la fonction
LoadLibrary().
- DLL_THREAD_ATTACH : Le processus est en train
de créer un nouveau thread et a déjà fait appel
à LoadLibrary().
- DLL_THREAD_DETACH : Le processus ferme un thread.
- DLL_PROCESS_DETACH : La DLL
est déchargée de la mémoire.
Voici donc un exemple de mise en oeuvre classique de la fonction
DllMain() :
BOOL
WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved) |
Dans notre DLL, nous n’aurons pas besoin d’aller plus loin dans l’exploitation de cette fonction.
Il nous faut aussi penser à inclure le header
nécessaire aux DLL :
#include
"Windows.h" |
Ensuite, nous devons écrire la fonction exportée par la DLL, que nous appelons VerifPwd(). Elle doit prendre comme argument une chaîne de caractères, et retourner ‘true’ si elle correspond au mot de passe, ‘false’ le cas contraire. Comme on aime bien avoir un code propre, on définit le mot de passe avec #define, afin qu’il soit plus facilement modifiable. Ce qui donne :
#define PWD "lol" bool
VerifPwd(char* pass) |
A ce stade, la compilation devrait fonctionner, et on obtient un fichier *.dll . Par contre, si l’on essaie de le désassembler, on remarque qu’aucune fonction n’est exportée. En effet, il faut indiquer au compilateur que VerifPwd() doit être exportée, et donc que ce n’est pas simplement une fonction utile à la DLL. Pour cela, il faut ajouter ‘extern "C" __declspec(dllexport)’ devant l’en-tête de chaque fonction exportée. Toujours pour plus de clarté dans le code, on utilise l’instruction #define :
#define DLLEXPORT extern "C" __declspec(dllexport) DLLEXPORT bool VerifPwd(char*
pass) |
On relance la compilation et tout fonctionne : on a désormais une DLL avec une fonction VerifPwd() exportée et utilisable par n’importe quel programme.
4) Écriture d’un programme utilisant notre DLL
Pour tester notre DLL, rien de tel qu’un programme appelant la fonction VerifPwd(). Pour que la DLL soit utilisable, il faut la copier dans le répertoire du programme appelant et la renommer par exemple en verif.dll.
Nous allons avoir besoin de nouvelles fonctions pour l’implémentation de notre DLL :
HMODULE
LoadLibrary( __LPCTSTR lpFileName // file name of module ); |
LoadLibrary() prend comme argument une chaîne contenant le nom de la DLL, la charge en mémoire, et retourne un handle vers cette dernière.
BOOL
FreeLibrary( __HMODULE hModule // handle to DLL module ); |
FreeLibrary() prend comme argument un handle vers la DLL et la décharge de la mémoire. Si l’opération n’a pas fonctionné, la fonction retourne 0.
FARPROC
GetProcAddress( __HMODULE hModule, ___// handle to DLL module __LPCSTR lpProcName __// function name ); |
GetProcAddress() prend comme arguments un handle vers la DLL et une chaîne contenant le nom de la fonction à appeler, et retourne l’adresse mémoire de cette dernière.
A l’aide de ces fonctions, nous pouvons aisément
écrire notre programme, dont voici la source commentée :
#include
<iostream> ___________________________//
header pour les sorties using namespace std; __________________________// toujours pour les sorties typedef
bool (CALLBACK* VerifPwd)(char *); ____//
définition d'une structure pour la fonction appelée int main() _______hDll = LoadLibrary("verif.dll"); _______// on charge la DLL en mémoire _______if(!hDll)
______________________________// si elle
n'est pas chargée... _______________if(!pVerifPwd)
____________________________________________________//
si ca n'a pas fonctionné... ______________________if(pVerifPwd(pass))
_________________________// appel de la fonction
de la DLL |
On compile, on test, et tout fonctionne.
Grâce à cet article, vous
avez donc programmé votre première DLL
et appris à écrire un programme utilisant des fonctions exportées
par une DLL. J’espère ainsi vous avoir
fait entrevoir une des multiples facettes de ce fantastique langage qu’est
le C.
Mais souvenez-vous, la programmation est principalement de la pratique et du
test. Alors désormais, c’est à vous de coder vos propres
DLL et de faire vos propres essais afin d’en
découvrir les nombreuses possibilités.
Dans mon prochain article nous approfondirons ces connaissances en programmant
des DLL permettant de logger les appels aux fonctions
exportées des DLL, et vous aurez besoin d’avoir
bien compris cet article.
Amusez-vous bien et bon coding de DLL
!
BY THESHADE
Copyright © 2004 ARENHACK - DHS