I. Création de la DLL▲
Pour commencer, créons le corps de la DLL par le menu Fichier/Nouveau/Autres, en terminant par un double-clic sur l'icône Expert dll. Vous remarquerez immédiatement le long commentaire inséré après la première ligne du code créé. Que nous explique-t-il ?
Remarque importante concernant la gestion de mémoire de DLL : ShareMem doit être la première unité de la clause USES de votre bibliothèque ET de votre projet (sélectionnez Projet-Voir source) si votre DLL exporte des procédures ou des fonctions qui passent des chaînes en tant que paramètres ou résultats de fonction. Cela s'applique à toutes les chaînes passées de et vers votre DLL – même celles qui sont imbriquées dans des enregistrements et classes. ShareMem est l'unité d'interface pour le gestionnaire de mémoire partagée BORLNDMM.DLL, qui doit être déployé avec vos DLL. Pour éviter d'utiliser BORLNDMM.DLL, passez les informations de chaînes avec des paramètres PChar ou ShortString. }
L'avertissement est très clair. Pour utiliser des variables de type String long dans une DLL créée avec Delphi, on doit passer par le gestionnaire de mémoire partagée BORLNDMM.DLL. ATTENTION : il est bien précisé que cela ne concerne QUE les String longs spécifiques de Delphi. Les PChar et les ShortString ne sont pas concernés.
Vous me répondrez: « Pourquoi pas ? » Et bien tout simplement pour des problèmes de compatibilité et de déploiement de logiciels. Toujours limiter au possible le nombre de fichiers à déployer chez le client est une bonne devise. Cela évite de retourner au bureau récupérer sur une disquette une DLL oubliée dans le programme d'installation. Il est donc prudent et judicieux d'utiliser des PChar ou des Shortstring comme on nous l'indique dans l'avertissement. Le problème, c'est si l'on passe directement un PChar comme paramètres, Delphi va se plaindre. Pourquoi ?
Tout d'abord, qu'est-ce qu'un PChar ? Voyons ce que dit l'aide Delphi !
Un PChar est un pointeur sur une chaîne à zéro terminal de caractères de type Char. Chacun des trois types caractère dispose d'un type de pointeur prédéfini : un PChar est un pointeur sur une chaîne à zéro terminal de caractères 8 bits. Un PAnsiChar est un pointeur sur une chaîne à zéro terminal de caractères 8 bits. Un PWideChar est un pointeur sur une chaîne à zéro terminal de caractères 16 bits. PChar est, avec les chaînes courtes, l'un des types chaîne qui existaient à l'origine dans le Pascal Objet. Il a été créé tout d'abord comme type compatible avec le langage C et l'API Windows.
Note : un PChar étant terminé par un caractère #0, n'oubliez pas que cela interdit la présence de ce caractère dans la chaîne, ce qui vous obligerait alors à utiliser le type String. Mais comme l'affichage passe par l'API Windows ne gérant que les PChar, le premier caractère #0 sera considéré comme fin de chaîne. (Merci à Merlin et RDM(Epita) pour cette précision.)
Delphi nous offre une compatibilité entre les string et les PChar. On peut ainsi écrire :
MonPChar := MaVariableSting;
Mais ça, ce n'est valable que pour Delphi. Un PChar étant un pointeur sur une chaîne, pour les autres langages, ça n'est ni plus ni moins qu'une adresse, pas une chaîne de caractères. La fin de cette chaîne est déterminée par la présence d'un caractère null, aussi appelé zéro terminal. En définissant un PChar, on définit (en quelque sorte), un début et une FIN. Je mets volontairement le mot FIN en majuscules, car c'est là le point le plus important de l'affaire.
Avec Delphi, on définit une variable de type string, autrement dit, une chaîne longue.
Pour les string longs, Delphi gère un offset négatif qui contient la longueur de la chaîne et un compteur de références qui servent à la gestion dynamique de la chaîne. De plus, Delphi ajoute toujours un #0 après le dernier char pour simplifier la compatibilité avec les PChar. (Merci à Merlin pour cette nouvelle précision.)
Quelle que soit la longueur de la chaîne que nous allons assigner à la variable, Delphi se chargera de l'allocation mémoire nécessaire pour contenir cette chaîne.
Par exemple :
2.
3.
4.
5.
var
S: String
;
begin
S := 'Une chaîne créée'
;
end
;
Le programmeur n'a pas à se soucier d'allouer une taille mémoire pour stocker cette chaîne. Delphi le fait pour nous. Il crée automatiquement un pointeur sur un emplacement mémoire (l'adresse de la suite de caractères représentant la chaîne) suivi des caractères assignés. On obtient alors un tableau de caractères que l'on peut représenter ainsi :
2.
3.
4.
5.
Car[0
] = adresse mémoire du tableau
Car[1
] = 'U'
Car[2
] = 'n'
...
Car[16
] = 'e'
Delphi gère aussi les modifications de taille. Ce qui veut dire que si je modifie la chaîne, pour la remplacer par une chaîne plus longue, Delphi va gérer dynamiquement cette augmentation de taille pour contenir la nouvelle suite de caractères. Ce que ne permet pas le C (par exemple). Pour créer une procédure 'propre' dans une DLL, il sera nécessaire de contrôler la taille de la zone mémoire utilisée. Pour cela, l'utilisation d'un type array of char est tout indiquée.
Revenons à notre problème de base qui est de passer des paramètres string de tailles variables à notre DLL. Cette DLL, que va-t-elle faire ? Elle va recevoir deux valeurs « chaînes » qu'elle devra modifier pour renvoyer au programme appelant un nom d'utilisateur avec le mot de passe que celui-ci aura saisi. Voilà le code de la DLL !
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
library
LoginPerso;
uses
windows,
controls,
UFrmLogin in
'UFrmLogin.pas'
{FrmMotPasse}
;
{$R *.res}
function
GetPassWord(UserName, PassWord: PChar; SizeUser, SizePass: Cardinal
): boolean
; stdcall
;
begin
with
TFrmMotPasse.CreateWithParams(UserName, PassWord, SizeUser, SizePass, nil
) do
begin
ShowModal;
result := ModalResult = mrOk;
free;
end
;
end
;
exports
GetPassWord;
begin
end
.
Comme vous le voyez dans la déclaration de la fonction, Username et Password sont des PChar. Deux autres paramètres sont nécessaires pour une création « propre », il s'agit de SizeUser et SizePass qui sont les capacités en caractères pour Username et Password. Cette DLL utilise elle-même un TForm pour permettre la saisie des infos. Je ne donnerai pas le détail de la construction de cette forme, car j'y utilise des composants que vous n'aurez peut-être pas. Pour l'exemple, vous pouvez créer simplement un formulaire de saisie à votre goût. Le plus important n'est pas l'interface utilisateur, mais le code utilisé. Pour information, voici une capture d'écran de ce que j'ai créé.
Le code de cette unité est des plus simple.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
private
FUserName, FPassWord: PChar;
public
constructor
CreateWithParams(Username, PassWord: PChar; SizeUser, SizePass: DWORD; AOwner: TComponent);
{Ça, c'est pour la partie Interface. La partie implémentation n'a que deux procédures. La première
est une surcharge du constructor auquel je rajoute des paramètres.
}
constructor
TFrmMotPasse.CreateWithParams(Username, PassWord: PChar;
SizeUser, SizePass: Cardinal
; AOwner: TComponent);
begin
inherited
Create(AOwner);
FUserName := UserName;
FPassWord := Password;
with
EdtUser do
begin
maxlength := SizeUser - 1
;
Text := FUserName;
end
;
with
EdtPass do
begin
maxlength := SizePass - 1
;
Text := FPassWord;
end
;
end
;
On commence par stocker les « pointeurs » PChar dans des variables Private pour pouvoir les retrouver à la fermeture de la fenêtre de saisie des informations. Ensuite, on définit la longueur maxi des zones de saisie correspondantes en fonction des paramètres reçus, et on initialise les TEdit avec les valeurs de Username et Password. À chaque longueur, je retranche 1 pour tenir compte du zéro terminal en sortie de procédure. Ceci n'est qu'une convention. Habituellement, les développeurs C et les API fonctionnent en supposant que la longueur maxi passée tient compte du caractère terminal. Tout n'est qu'une question de commentaire dans la procédure.
La deuxième et dernière code l'évènement OnClick du bouton OK
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
procedure
TFrmMotPasse.btnOkClick(Sender: TObject);
begin
if
EdtUser.Text = ''
then
begin
MessageBox(handle, 'Aucun utilisateur saisi !'
, 'Avertissement'
, MB_OK or
MB_ICONWARNING);
modalresult := mrCancel;
end
else
begin
StrPLCopy(FUserName, EdtUser.Text, EdtUser.MaxLength);
StrPLCopy(FPassWord, EdtPass.Text, EdtPass.MaxLength);
Modalresult := mrOk;
end
;
end
;
StrPLCopy permet de transférer le contenu d'une chaîne dans un PChar, tout en tenant compte d'une longueur de chaîne maxi. Ainsi, on est sûr de ne pas « déborder » la zone mémoire réservée au PChar. Comme FUserName est égal à notre UserName passé en paramètre, en transférant le texte dans FUserName, c'est comme si on le transférait directement à UserName dans la fonction de la DLL. En remontant encore d'un niveau, UserName dans la DLL est le même « pointeur » que UserName dans le code qui appelle la DLL. Ainsi, nous renseignons directement le PChar de départ. On n'a pas transféré de chaînes de caractères, mais on a tout simplement rempli une zone mémoire définie dans le code qui appelle la DLL. Et le tour est joué !
II. Utilisation de la DLL▲
L'utilisation de la DLL est très simple. Dans votre code Delphi, soit vous ajoutez une déclaration external à votre code, soit vous appelez la DLL de fonction dynamique.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
// function GetPassWord(UserName, PassWord: PChar;
SizeUser, SizePass: DWORD): boolean
; stdcall
; external
'LoginPerso.dll'
;
{Méthode d'appel dynamique}
procedure
TForm1.Button1Click(Sender: TObject);
type
TMyProc = function
(UserName, PassWord: PChar; SizeUser, SizePass: DWORD):
boolean
; stdcall
;
var
U, P: array
[0
..49
] of
Char
;
Handle: THandle;
MyProc: TMyProc;
begin
Handle := loadlibrary('LoginPerso.dll'
);
if
Handle <> 0
then
begin
try
@MyProc := GetProcAddress(Handle, 'GetPassWord'
);
if
@MyProc <> nil
then
begin
U := 'Nom_User'
;
P := 'Pass_User'
;
if
MyProc(U, P, sizeof(U), sizeof(P)) then
ShowMessage(U + ':'
+ P);
end
;
Finally
FreeLibrary(Handle); //Assure le déchargement de la dll
end
;
end
else
ShowMessage('Impossible de charger la DLL'
);
end
;
La méthode de chargement statique est plus discutable, car le code est chargé par le programme même si la fonction ne doit pas être utilisée lors de l'utilisation finale.
Tout repose sur la compatibilité entre les PChar et les array of char. Chaque DLL Windows que vous utilisez et qui demande un paramètre PChar doit en fait passer un paramètre array of char de taille définie avec le maximum que pourra contenir le PChar dans la DLL. Par exemple, l'API GetWindowsDirectory vous demande un paramètre PChar que vous devez passer sous forme de tableau avec une taille définie pour obtenir un résultat correct. La taille est également passée en paramètre avec MAX_PATH.
2.
3.
4.
5.
6.
7.
function
DossierWindows: string
;
var
WinDir: array
[0
..MAX_PATH] of
char
;
begin
GetWindowsDirectory(Windir, MAX_PATH);
result := StrPas(Windir);
end
;
La fonction de notre DLL agit de la même manière. On lui passe un array de char de taille 50. Dans notre DLL, nous avons limité la taille de saisie à 50 (-1 pour le null de fin) caractères. Aucun débordement possible !
Il est également possible d'utiliser une autre version de notre code. StrAlloc et StrDispose permettent l'allocation de mémoire pour les PChar que nous pouvons alors utiliser comme type de départ. Par rapport à l'utilisation d'un array of char, cela nous oblige à rajouter quatre lignes à notre code, mais il paraît bon de présenter l'autre possibilité (merci à RDM(Epita) pour son rappel).
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
procedure
TForm1.Button1Click(Sender: TObject);
var
U, P: PChar;
begin
Hdle := loadlibrary('DllLogin.dll'
);
if
Hdle <> 0
then
begin
try
@MyProc := GetProcAddress(Hdle, 'GetPassWord'
);
if
@MyProc <> nil
then
begin
try
U := StrAlloc(50
);
P := StrAlloc(50
);
StrPLCopy(U, 'Username'
, length('Username'
));
StrPLCopy(P, 'Password'
, length('Password'
));
if
MyProc(U, P, 50
, 50
) then
ShowMessage(U + ':'
+ P);
finally
StrDispose(U);
StrDispose(P);
end
;
end
;
finally
FreeLibrary(Hdle);
end
;
end
else
ShowMessage('Impossible de charger la DLL'
);
end
;
Voilà, j'espère que ces quelques explications vous aideront dans votre travail !!
Bonne programmation et à bientôt !