Tutorial 17: Les DLL
(Dynamic Link Libraries)
Bibliothèques de Liaison, Dynamiques

Dans ce Tutorial, nous allons voir les DLLs, ce qu'elles sont et comment les créer.
Vous pouvez télécharger l'exemple ici.
 

Théorie:

Si vous programmez depuis un petit moment déjà, vous constaterez que les programmes que vous avez l'habitude d'écrire ont quelques routines de code en communs. C'est vraiment une perte de temps de les récrire à chaque fois que vous commencez à développer de nouveaux programmes. Revenons en arrière, aux vieux jours du DOS, les programmeurs ont faits en sorte de stocker les routines les plus généralement employées dans une ou plus bibliothèques. Quand ils veulent utiliser les fonctions, ils ont juste à lier la bibliothèque avec le fichier d'objet, et le linker extrait les fonctions de la bibliothèque et les insère dans le fichier final exécutable. Ce processus est appelé la jonction statique. Les bibliothèques 'Runtime du C' sont de bons exemples. L'inconvénient de cette méthode c'est que vous avez des fonctions identiques dans chaque programme qui les appelle. Votre espace disque est gaspillé stockant plusieurs copies identiques des fonctions. Mais pour les programmes sous DOS, cette méthode est tout à fait valable puisqu'il n'y a généralement qu'un seul programme en action dans la mémoire. Ainsi il n'y a aucune perte précieuse de mémoire.
Sous Windows, la situation devient beaucoup plus critique parce que vous pouvez avoir plusieurs programmes tournant simultanément. La mémoire sera rapidement saturée si votre programme est vraiment grand. Windows a une solution pour ce type de problème : les bibliothèques de liaison dynamiques. Une bibliothèque de liaison dynamique est une sorte de regroupement commun de plusieurs fonctions. Windows ne chargera pas en mémoire plusieurs copies d'une dll, même dans le cas où il y a beaucoup de programmes tournant en même temps. Il fera seulement une copie du dll que le programme emploie dans la mémoire. Et je dois clarifier un peu ce point. En réalité, tous les processus qui emploient la même dll, auront leurs propres copies de cette dll. C'est comme si il y avait plusieurs copies de cette dll en mémoire. Mais en réalité, Windows rend ça magique avec la pagination et tous les morceaux de processus ayant le même code dll. Ainsi en mémoire physique, il y a uniquement une copie du code de la dll. Cependant, chaque process aura sa propre section de données unique du dll.
Le programme se lie avec une DLL pendant l'exécution, à la différence de la vieille bibliothèque statique. C'est pourquoi on appelle la bibliothèque de liaison dynamique. Vous pouvez aussi vous défaire d'une DLL pendant l'exécution quand vous n'en avez plus besoin. Si ce programme est le seul à employer la DLL, il la virera immédiatement de la mémoire. Mais si la DLL est toujours employée par d'autres programmes, la DLL reste en mémoire tant que le dernier programme qui l'emploie s'en défasse.
Cependant, le linker a un travail plus difficile quand il exécute l'adresse fixups pour le fichier final exécutable. Puisqu'on ne peut pas "extraire" les fonctions et les insérer complètement dans le fichier final exécutable, d'une façon ou d'une autre on doit stocker assez d'informations sur la DLL et ses fonctions dans le fichier executable final pour être capable de charger correctement la DLL pendant l'exécution.
C'est là ! que la bibliothèque d'importation entre en jeu. Une bibliothèque d'importation contient les informations de la DLL qu'il représente. Le linker peut extraire des bibliothèques d'importation les renseignements dont il a besoin. Quand le loader Windows charge le programme en mémoire, il voit que le programme se lie avec une DLL donc il essai de trouver cette DLL et la réécrit aussi dans l'espace d'adresse du process(du programme), puis exécute les adresses hors du programme pour les fonctions Call qui appellent la DLL.
Vous pouvez vouloir charger la DLL vous-même sans compter sur le loader (chargeur) de Windows. Cette méthode a son pour et son contre : En comparant les avantages / inconvénients d'un appel à LoadLibrary, nous entrons maintenant dans les détails pour créer une DLL.
Le code suivant est le squelette de la DLL. (DLL skeleton.)

;--------------------------------------------------------------------------------------
;                           DLLSkeleton.asm
;--------------------------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
.code
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
        mov  eax,TRUE
        ret
DllEntry Endp
;---------------------------------------------------------------------------------------------------
;                                                C'est une fonction factice
; Ça ne fait rien. Je l'ai mise ici pour montrer où vous pouvez insérer des fonctions à l'intérieur d'une DLL.
;----------------------------------------------------------------------------------------------------
TestFunction proc
    ret
TestFunction endp

End DllEntry



;-------------------------------------------------------------------------------------
;                              DLLSkeleton.def
;-------------------------------------------------------------------------------------
LIBRARY   DLLSkeleton
EXPORTS   TestFunction
 

Ce tout petit programme ci-dessus est le squelette de la DLL. Chaque DLL doit avoir une fonction d'entrypoint. Windows appellera la fonction d'entrypoint chaque fois que :

DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
        mov  eax,TRUE
        ret
DllEntry Endp

Vous pouvez nommer la fonction d'entrypoint comme vous le souhaitez tant que vous avez une FIN correspondante à 'END ' . Cette fonction prend trois paramètres, dont seulement les deux premier sont importants.
hInstDLL est l'handle du module(de la fonction) de la DLL. Ce n'est pas le même que l'instance handle du process. Vous devez garder cette valeur si vous avez besoin de l'employer plus tard. Vous ne pourrez pas l'obtenir de nouveau aussi facilement.
reason peut être une des quatre valeurs :

Renvoyez TRUE dans eax si vous souhaitez que la DLL continue son exécution. Si vous renvoyez FALSE, la DLL ne sera pas chargé. Par exemple, si votre code d'initialisation doit réserver de la mémoire et qu'il ne peut pas faire ça avec succès, la fonction d'entrypoint doit renvoyer FALSE pour indiquer que la DLL ne peut pas être lancée.
Vous pouvez placer vos fonctions dans la DLL après la fonction Entrypoint ou même avant çà. Mais si vous voulez qu'elles soient CALLables par d'autres programmes, vous devez mettre leurs noms dans la liste d'exportation du fichier de définition de module (.def).
Une DLL a besoin d'un fichier de définition de module dans son processus liée à son développement. Nous y jetterons un coup d'œil maintenant.

LIBRARY   DLLSkeleton
EXPORTS   TestFunction

Normalement vous devez avoir la première ligne. La déclaration LIBRARY définit le nom du module interne (la fonction souhaitée) de la DLL. Vous devez correspondre avec ce module grâce au nom du fichier de la DLL.
La déclaration EXPORTS dit au linker quelles fonctions sont en train de tourner dans la DLL, c'est-à-dire quelles fonctions sont déjà utilisées par d'autres programmes. Dans l'exemple, nous voulons que d'autres modules soient capables d'appeler TestFunction, donc nous mettons son nom dans la déclaration EXPORTS.
Un autre changement c'est le commutateur du linker. Vous devez mettre le commutateut /DLL et /DEF:<votre nom de fichier def> dans votre linker de commutation comme cela :

link /DLL /SUBSYSTEM:WINDOWS /DEF:DLLSkeleton.def /LIBPATH:c:\masm32\lib DLLSkeleton.obj

Les commutateurs d'assembleur sont les mêmes, à savoir /c /coff /Cp. Ainsi après que vous ayez lié le fichier objet, vous obtiendrez *.DLL et *.lib. Le *.lib est la bibliothèque d'importation que vous pouvez employer pour vous lier avec d'autres programmes qui emploient les fonctions de votre DLL.
Ensuite je vous montrerai comment employer LoadLibrary pour charger une DLL.




;---------------------------------------------------------------------------------------------
;                                      UseDLL.asm
;----------------------------------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib

.data
LibName db "DLLSkeleton.dll",0
FunctionName db "TestHello",0
DllNotFound db "Cannot load library",0
AppName db "Load Library",0
FunctionNotFound db "TestHello function not found",0

.data?
hLib dd ?                                         ; l' handle de la librairie (l' handle de la DLL)
TestHelloAddr dd ?                        ; l'adresse de la function TestHello

.code
start:
        invoke LoadLibrary,addr LibName
;---------------------------------------------------------------------------------------------------------
; Appelle LoadLibrary avec le nom de la DLL désirée. Si l'appel est couronné de succès
; Il renverra l'handle de la bibliothèque (DLL). Sinon, il renverra le NULL.
; Vous pouvez passer l'handle de la bibliothèque à GetProcAddress ou n'importe quelle fonction qui en a besoin
; de l'handle d'une DLL comme paramètre.
;------------------------------------------------------------------------------------------------------------
        .if eax==NULL
                invoke MessageBox,NULL,addr DllNotFound,addr AppName,MB_OK
        .else
                mov hLib,eax
                invoke GetProcAddress,hLib,addr FunctionName
;-------------------------------------------------------------------------------------------------------------
; Quand vous obtenez l'handle de la bibliothèque, vous le passez à GetProcAddress avec l'adresse
; du nom de la fonction de la DLL que vous souhaitez appeler. Ceci renvoie l'adresse
; de la fonction si tout s'est passé avec succès. Autrement, il renvoie le NULL.
; Les adresses des fonctions ne changent pas à moins que vous ne déchargiez puis rechargiez la bibliothèque.
; Donc vous pouvez les placer dans des variables globales pour de futures utilisations.
;-------------------------------------------------------------------------------------------------------------
                .if eax==NULL
                        invoke MessageBox,NULL,addr FunctionNotFound,addr AppName,MB_OK
                .else
                        mov TestHelloAddr,eax
                        call [TestHelloAddr]
;-------------------------------------------------------------------------------------------------------------
; Ensuite, vous pouvez appeler la fonction avec un appel simple avec la variable contenant
; l'adresse de la fonction comme l'opérande.
;-------------------------------------------------------------------------------------------------------------
                .endif
                invoke FreeLibrary,hLib
;-------------------------------------------------------------------------------------------------------------
; Quand vous n'avez plus besoin de la bibliothèque, vous la déchargez avec FreeLibrary.
;-------------------------------------------------------------------------------------------------------------
        .endif
        invoke ExitProcess,NULL
end start

Donc vous pouvez voir que l'utilisation de LoadLibrary est un peu plus impliquée mais c'est aussi plus flexible.


[Iczelion's Win32 Assembly Homepage]


Traduit par Morgatte