Création et utilisation d'une DLL sous C++ Builder

LFE

Ce document a pour but de présenter la création et l'utilisation d'une DLL suis C++ Builder. ♪

- Titre : Création et utilisation d'une DLL sous C++ Builder

- Auteur : LFE

- Parution : 13 octobre 2002

- Licence : Ce document est issu de https://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.

Sommaire

- Avant-propos

- I. Création de la DLL

- II. Utilisation statique de la DLL

- III. Utilisation dynamique de la DLL

- IV. Passage de paramètres à une fonction exportée

- V. Utilisation statique d'une DLL « étrangère »

- VI. Utilisation d'une classe contenue dans une DLL

- VII. TForm dans la DLL : n'afficher que la fenêtre principale de l'application dans la barre des tâches.

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);
}
}