Avant-propos▲
Travaillant sous C++ Builder Enterprise 6.0 en version anglaise, tous les noms des menus, options…. seront en anglais. L'utilisation de cette langue pour le code et les commentaires éventuels sont simplement le résultat d'une habitude de codage.
J'espère que l'explication qui va suivre est claire. Si ce n'était pas le cas, je suis ouvert à toute critique ou remarque, pour autant qu'elle soit constructive.
Le code est minimal dans la mesure où le but est d'expliquer un principe et non de réaliser une application complète.
I. Création de la DLL▲
Créer un nouveau projet. Dans le menu File, sélectionner New, Other … Dans la fenêtre qui apparaît à ce moment, sélectionner le « DLL Wizard » :
Une seconde fenêtre apparaît :
La DLL que nous allons créer sera en C++ et elle contiendra une fenêtre à afficher, la case VCL doit donc être cochée. Cette case doit être cochée dès que l'on utilise un élément de la VCL.
L'assistant BCB génère le code suivant :
//---------------------------------------------------------------------------
#include
<vcl.h>
#include
<windows.h>
#pragma hdrstop
//---------------------------------------------------------------------------
// Important note ... bla bla ...
//---------------------------------------------------------------------------
#pragma argsused
int
WINAPI DllEntryPoint(HINSTANCE hinst, unsigned
long
reason, void
*
lpReserved)
{
return
1
;
}
//---------------------------------------------------------------------------
Créer ensuite une fenêtre minimale.
Dans le menu File, New, sélectionner Form.
Déposer dessus un Button :
Le code exécute par le clic sur le bouton est le suivant :
void
__fastcall TForm2::
Button1Click(TObject *
Sender)
{
Close();
}
Modifier ensuite le code du projet de la DLL de la façon suivante :
//---------------------------------------------------------------------------
#include
<vcl.h>
#include
<windows.h>
#pragma hdrstop
#include
"unit2.h"
extern
"C"
__declspec(dllexport) __stdcall int
LoadForm();
#pragma argsused
int
WINAPI DllEntryPoint(HINSTANCE hinst, unsigned
long
reason, void
*
lpReserved)
{
return
1
;
}
//---------------------------------------------------------------------------
int
__stdcall LoadForm()
{
TForm2 *
Form2;
Form2 =
new
TForm2(NULL
);
Form2->
ShowModal();
delete
Form2;
return
1
;
}
//---------------------------------------------------------------------------
La fonction LoadForm sera la fonction qui sera exportée de la DLL et pourra être appelée depuis le programme principal, c'est pour cela qu'elle est déclarée dllexport.
Sauvegarder le tout et compiler.
Un fichier LIB et une DLL sont créés.
II. Utilisation statique de la DLL▲
Créer un nouveau projet.
Dans le menu File, sélectionner New, Application.
À nouveau, créer une application minimale : une fenêtre avec un bouton.
Modifier ensuite le code de la façon suivante :
//---------------------------------------------------------------------------
#include
<vcl.h>
#pragma hdrstop
#include
"Unit1.h"
extern
"C"
__declspec(dllimport
) __stdcall int LoadForm();
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource
"*.dfm"
TForm1 *
Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::
TForm1(TComponent*
Owner)
:
TForm(Owner)
{
}
//---------------------------------------------------------------------------
void
__fastcall TForm1::
Button1Click(TObject *
Sender)
{
int
iRet;
iRet =
LoadForm();
}
//---------------------------------------------------------------------------
Cette fois, la fonction LoadForm est déclarée dllimport étant donné qu'elle n'est plus exportée de la DLL, mais importée dans le programme.
Ajouter la librairie d'import dans le projet (le LIB).
Dans le menu Project, Add To Project … Sélectionner le fichier LIB.
Sauvegarder le tout, compiler et exécuté.
Lorsque l'on appuie sur le bouton de la première fenêtre, la seconde apparaît et disparaît lorsque l'on clique sur le bouton.
III. Utilisation dynamique de la DLL▲
Créer un nouveau projet.
Dans le menu File, sélectionner New, Application.
À nouveau, créer une application minimale : une fenêtre avec un bouton.
Modifier ensuite le code de la façon suivante :
//---------------------------------------------------------------------------
#include
<vcl.h>
#pragma hdrstop
#include
"Unit1.h"
#pragma package(smart_init)
#pragma resource
"*.dfm"
TForm1 *
Form1;
typedef
int
(__stdcall *
MYDLLFUNC)(void
);
//---------------------------------------------------------------------------
__fastcall TForm1::
TForm1(TComponent*
Owner) : TForm(Owner)
{
}
//---------------------------------------------------------------------------
void
__fastcall TForm1::
Button1Click(TObject *
Sender)
{
HINSTANCE hinstDLL;
MYDLLFUNC ImpFuncDLL;
int
iRet;
if
((hinstDLL=
LoadLibrary("Project1.dll"
))) {
ImpFuncDLL =
GetProcAddress(hinstDLL, "LoadForm"
);
if
(ImpFuncDLL) {
iRet =
ImpFuncDLL();
}
FreeLibrary(hinstDLL);
}
}
//---------------------------------------------------------------------------
typedef int (__stdcall *MYDLLFUNC)(void); est la déclaration du type de la fonction qui est importée de la DLL.
La fonction GetProcAdress renvoie un pointeur sur une fonction. Le cast vers le bon type est nécessaire pour pouvoir passer les paramètres éventuels à la fonction importée.
LoadLibrary charge la DLL en mémoire, et retourne un handle permettant d'y accéder.
GetProcAdress prend en paramètre le handle de la DLL et le nom d'une fonction. Si la fonction est trouvée dans la DLL, GetProcAdress renvoie son adresse, sinon NULL.
L'appel de la fonction importée se fait de façon tout à fait classique, non par son nom, mais par le nom du pointeur associé.
Ne pas oublier de décharger la DLL quand elle n'est plus utilisée, par FreeLibrary.
IV. Passage de paramètres à une fonction exportée▲
Le passage de paramètres à une fonction exportée d'une DLL se fait de façon tout à fait normale dans le cas de l'utilisation statique. Seule l'utilisation dynamique présente une particularité : il faut faire un cast explicite du pointeur de retour de GetProcAdress.
Code de la fonction dcontenue dans la DLL :
extern
"C"
__declspec(dllexport) __stdcall TDateTime AddDayToDate(TDateTime dt, int
nbDay);
// Add a number of days to a Date and returns the new date (dummy sample)
TDateTime __stdcall AddDayToDate(TDateTime dt, int
nbDay)
{
dt +=
nbDay;
return
dt;
}
Code d'appel dans le programme :
typedef
TDateTime (__stdcall *
MYDLLFUNC2) (TDateTime dt, int
nbDay);
void
__fastcall TForm3::
Button1Click(TObject *
Sender)
{
HINSTANCE hinstDLL;
MYDLLFUNC2 ImpFuncDLL;
TDateTime dtAfter, dtBefore;
int
iRet;
if
((hinstDLL=
LoadLibrary("Project1.dll"
))) {
ImpFuncDLL =
(MYDLLFUNC2)GetProcAddress(hinstDLL, "AddDayToDate"
);
if
(ImpFuncDLL) {
dtBefore =
TDateTime::
CurrentDateTime();
dtAfter =
ImpFuncDLL(dtBefore,1
);
}
FreeLibrary(hinstDLL);
}
}
V. Utilisation statique d'une DLL « étrangère »▲
Il est nécessaire de disposer de deux choses :
- la DLL ;
- le fichier d'en-tête, ou du moins les noms des fonctions contenues dans la DLL et leurs paramètres, afin de pouvoir en déclarer les prototypes.
Ce fichier d'en-tête doit être ajouté à tous les fichiers source utilisant les fonctions de la DLL.
Création de la librairie d'import.
dans une fenêtre DOS, exécuter la commande suivante IMPLIB <nom_de_la_DLL>.LIB <nom_de_la_DLL>.DLL
Ajouter la librairie d'import dans le projet. Dans le menu Project, Add To Project … Sélectionner le fichier LIB.
Les fonctions déclarées s'appellent de façon tout à fait classique.
VI. Utilisation d'une classe contenue dans une DLL▲
L'utilisation d'une classe contenue dans une DLL est fort semblable à l'utilisation statique d'une DLL.
Création de la classe dans la DLL
foo.h :
#ifdef __DLL__
#define IMPORT_EXPORT __declspec(dllexport)
#else
#define IMPORT_EXPORT __declspec(dllimport)
#endif
//---------------------------------------------------------------------------
IMPORT_EXPORT class
foo {
public
:
int
N1, N2, Result;
void
__stdcall AddN();
int
__stdcall GetResult();
foo();
}
;
foo.cpp :
#include
"foo.h"
foo::
foo()
{
N1 =
0
;
N2 =
0
;
Result=
0
;
}
void
__stdcall foo::
AddN()
{
Result =
N1 +
N2;
}
int
__stdcall foo::
GetResult()
{
return
Result;
}
__DLL__ est une macro définie par le compilateur lors de la création d'un projet DLL.
La façon dont est défini le code de foo.h permet de n'avoir qu'un seul source pour la DLL et pour le header qui doit être inclus au projet principal.
Ajouter la librairie d'import dans le projet.
Dans le menu Project, Add To Project … Sélectionner le fichier LIB.
Lors de l'instanciation de la classe contenue dans la DLL, l'utilisation est tout à fait classique.
void
__fastcall TForm3::
Button2Click(TObject *
Sender)
{
int
i Ret;
foo *
Foo;
Foo =
new
foo();
Foo->
N1 =
5
;
Foo->
N2 =
15
;
Foo->
AddN();
iRet =
Foo->
GetResult();
delete
Foo;
}
VII. TForm dans la DLL : n'afficher que la fenêtre principale de l'application dans la barre des tâches.▲
Lorsqu'une Form est contenue dans une DLL, l'utilisation de celle-ci fait apparaître dans la barre des tâches deux boutons :
- celui de la fenêtre principale de l'application ;
- celui de la fenêtre contenue dans la DLL.
Pour résoudre ce petit problème, il suffit d'attribuer le Handle de l'Application principale à celui de la DLL.
Dans le code de la DLL, cela se traduit par :
extern
"C"
__declspec(dllexport) __stdcall int
LoadForm(HWND HWnd);
//.....
int
__stdcall LoadForm(HWND HWnd)
{
HWND OldHandle;
OldHandle =
Application->
Handle;
Application->
Handle =
HWnd;
Form2 =
new
TForm2(NULL
);
Form2->
ShowModal();
delete
Form2;
Application->
Handle =
OldHandle;
return
1
;
}
et dans le code du programme principal :
typedef
int
(__stdcall *
MYDLLFUNC) (HWND HWnd);
void
__fastcall TForm3::
Button1Click(TObject *
Sender)
{
HINSTANCE hinstDLL;
MYDLLFUNC ImpFuncDLL;
if
((hinstDLL=
LoadLibrary("Project1.dll"
))) {
ImpFuncDLL =
(MYDLLFUNC)GetProcAddress(hinstDLL, "LoadForm"
);
if
(ImpFuncDLL) {
iRet =
ImpFuncDLL(Application->
Handle);
}
FreeLibrary(hinstDLL);
}
}