Guide Pascal et Delphi
Guide Pascal et Delphi
Date de publication : 10/04/2000 , Date de mise à jour : 01/04/2008
XVIII. DLL
XVIII-A. Introduction
XVIII-B. Ecriture des DLLs & Utilisation
XVIII-B-1. DLL de fonction
XVIII-B-2. Théorie & Subtilité
XVIII-B-3. DLL de Classe
XVIII-B-4. DLL de Composant
XVIII-B-5. DLL & Chaîne de caractère
XVIII-C. Chargement statique/dynamique
XVIII-D. DLL et Autres Langages
XVIII-E. Conclusion
XVIII. DLL
DLL, Dynamic Library Link ou en français lien dynamique vers une librairie. Le fichier
DLL est cette librairie. Le but étant au départ de permettre aux développeurs de
bénéficier de fonction déjà existante et aussi de décharger la mémoire. Les DLL
contiennent en effet des ressources qu'elle peuvent partager avec plusieurs programmes.
C'est ressources permettent d'avoir accès à des fonctions, des composants...
XVIII-A. Introduction
Qu'est ce qu'une DLL me direz-vous ? Une dll est un fichier contenant des ressources
et la capacité de mettre à disposition ces ressources pour les programmeurs et donc
pour les applications. Une DLL peut contenir des fonctions (exemple des fonctions
mathématiques), une classe, des composants ou d'autre chose comme des icônes.
Pour ce qui est de son utilité, on peut en distinguer trois.
Pour ce qui est de son utilité, on peut en distinguer trois.
- La principale est qu'elle permet de partager ses ressources avec tout le monde. Vous pouvez distribuer ou récupérer une dll et ensuite l'utiliser ses ressources. Exemple si vous créez ou récupérer une DLL contenant des fonctions mathématiques, vous n'avez plus besoin de les réaliser dans votre application, il suffit de les importer. Leur utilisation se fait alors comme celle que l'on trouve dans Delphi comme sqrt.
- La seconde est qu'elle permet de découper son application en morceau afin d'améliorer la maintenance et le débogage. Si vous modifier une partie du code dans une dll, il vous suffit de redistribuer votre dll pour mettre l'application à jour. Cela permet aussi de séparer par fonctionnalité votre code. Je dois dire que l'intérêt de ce point est un peu flou. Il est quasiment inexistant pour les petites applications, il suffit de faire plusieurs unités et on peut aussi utiliser l'héritage, je ne parle pas ici de l'héritage des classes quoique mais de celui des unités(Pour Rappel : on peut inclure des unités dans son projet en spécifiant qu'ils ne sont qu'une copie de l'original ou un lien, ce qui implique qu'elles se mettent à jour si l'original est modifié). Reste le problème de la compilation qui concerne tout le projet au contraire de la DLL où seul la dll concernée est à recompiler.
- Le troisième est que l'on peut charger dynamiquement une DLL. Quand une DLL est chargée dynamiquement dans une application, elle ne réside en mémoire que lorsqu'elle est utilisée (appelé) et ensuite la mémoire est libérée (la dll est déchargée).
XVIII-B. Ecriture des DLLs & Utilisation
Dans cette partie, nous allons voir comment écrire et utiliser une DLL de fonction,
de Classe et pour terminer de composant.
XVIII-B-1. DLL de fonction
Pour ceux qui préfèrent la théorie avant la pratique, regardez les chapitres suivant.
Pour faire simple, utilisons l'expert DLL, Fichier -> nouveau -> Expert DLL.
Vous obtiendrez le prototype d'une Dll avec Delphi
Vous obtiendrez le prototype d'une Dll avec Delphi
Nous allons commencer une DLL de fonction Mathématique, libre à vous de rajouter
d'autre fonction par la suite. Enregistrer le projet sous le nom LibMaths.
Notre première fonction sera la fonction factorielle. Bref rappel de Math, la factorielle de 1 vaut 0 et la factorielle de 0 vaut 1. On note l'opération factorielle : n! où n est un nombre entier positif et ! l'opérateur. Le produit factoriel de n vaut n multiplier par le produit factoriel de n-1 (pour les esprits vifs que vous êtes, cette définition vous fait penser à la récursivité). Exemple : 3! = 3*2! = 3*2*1! soit 3*2*1 donc 6. et 4! = 4*3! = 4*6 soit 24. Pour ce qui ne connaissent pas la récursivité, pas d'inquiétude car ce n'est pas le sujet du cours, l'essentiel est de comprendre le principe.
Notre première fonction sera la fonction factorielle. Bref rappel de Math, la factorielle de 1 vaut 0 et la factorielle de 0 vaut 1. On note l'opération factorielle : n! où n est un nombre entier positif et ! l'opérateur. Le produit factoriel de n vaut n multiplier par le produit factoriel de n-1 (pour les esprits vifs que vous êtes, cette définition vous fait penser à la récursivité). Exemple : 3! = 3*2! = 3*2*1! soit 3*2*1 donc 6. et 4! = 4*3! = 4*6 soit 24. Pour ce qui ne connaissent pas la récursivité, pas d'inquiétude car ce n'est pas le sujet du cours, l'essentiel est de comprendre le principe.
-
Pour partir sur de bonne base, votre projet doit ressembler à ceci :
library
LibMaths;{
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.
}
uses
SysUtils, Classes;{$
R
*.res
}
begin
end
. -
Entre le windows et le begin, nous allons coder notre fonction factorielle soit :
function
Factorielle(n :integer
):integer
;begin
//
On
n'aurait
pu
traité
le
cas
0
et
1
séparement
mais
cela
fait
un
test
de
plus
if
n =0
then
Result :=1
else
Result := n * Factorielle(n-1
);end
;Vérifiez bien que la fonction puisse s'arrêter quand vous faites de la récursivité sinon vous créez une boucle infinie. -
Maintenant, nous allons exporter notre fonction pour qu'elle puisse être utilisée par d'autre application. Entre le end de la fonction et le begin du projet, tapez :
exports
Factorielle;Le code source complet doit ressembler à ceci. Mais attention, il ne s'agit pas d'un exécutable, vous ne pouvez que le compiler. Pour ce faire, Tapez CTRL+F9 ou Projet -> Compiler LibMaths.library
LibMaths;uses
SysUtils, Classes;function
Factorielle(n :integer
):integer
;begin
if
n =0
then
Result :=1
else
Result := n * Factorielle(n-1
);end
;exports
Factorielle;begin
end
.Nous allons maintenant nous attaquer à la programmation d'une application utilisant cette dll. Pour cela enregistrer votre projet si ce n'est déjà fait et commencez un nouveau projet.
Dans la Form ajouter un EditBox pour la saisie de l'utilisateur et un Label ou EditBox pour afficher le résultat plus d'autres labels pour documenter votre application. Ajoutez aussi deux boutons l'un pour calculer et l'autre pour fermer. Pour le bouton Fermer vous pouvez utiliser un BitBt (onglet : Supplément) et positionner sa propriété Kind à bkClose.
-
Entre Implémentation et les directives de compilation, importez la fonction. L'importation se fait en reprenant la déclaration de la fonction soit ici : function Factorielle(n : integer): integer; Attention à la casse (il faut le f majuscule) ajouter le mot reservé external 'nom du fichier dll'; sans l'extension.
implementation
function
Factorielle(n :integer
):integer
;external
'
LibMaths
'
;{$
R
*.DFM
}
-
On peut maintenant utiliser cette fonction, dans l'événement onclick du bouton Calculer, ajouter le code suivant :
Remarque : Il n'y a pas de test sur la saisie de l'utilisateur dans un souci de clarté !procedure
TForm1.btTestClick(Sender: TObject);var
n :integer
;begin
n := StrToInt(Edit1.Text); lbResultat.Caption := IntToStr(Factorielle(n));end
; - Exécutez et testez votre application. Notez que le fichier dll doit se trouver dans le même répertoire que l'application l'utilisant ou dans le répertoire window ou window/systeme (à éviter cependant sauf cas particulier comme les dll de windows :) ).
Le code source complet ci dessous.
|
XVIII-B-2. Théorie & Subtilité
Si vous avez regardé la partie au-dessus, le premier mot nouveau fut library.
Ce mot indique au compilateur qu'il ne s'agit pas d'un programme principal
(contenant main) et donc sans point d'entrée de type winmain. Ensuite la partie
entre begin et end. correspond comme pour les projets à la partie
initialisation. Dans la partie initialisation, vous pouvez initialiser les
variables globales. Voilà pour les détails.
exports : permet de lister les fonctions à rendre visible/accessible depuis l'extérieur.
On peut rajouter y ajouter des options :
exports : permet de lister les fonctions à rendre visible/accessible depuis l'extérieur.
On peut rajouter y ajouter des options :
-
name : renomme la fonction exportée, par exemple quand le nom de la fonction existe déjà ailleurs.
exporte la fonction toto sous le nom titi, c'est titi qu'il faudra utiliser dans les applications utilisant cette dll. Attention à la casse.exports
Toto name'
titi
'
;exporte la fonction toto sous le nom titi, c'est titi qu'il faudra utiliser dans les applications utilisant cette dll. Attention à la casse.
-
index : il spécifie un index pour la fonction exporter. Toutes les fonctions sont indexées, si on change l'index d'une fonction les index des fonctions suivantes reprennent à partir de celui-ci. N'est plus utilisé en environnement 32 bits (win98 et +) car le gain de performance est négligeable.
exports
Totoindex
5
;La fonction Toto sera accessible par l'indice 5.
|
Ces options sont cumulables, on peut exporter une fonction par nom et par index
(Toto name 'titi' index 5;).
Les noms des fonctions exportées sont sensibles à la casse (il y a une différence entre majuscule et minuscule).
Convention : La convention permet de spécifier la gestion du passage de paramètre autrement dit comment les paramètres vont être gérés. Elles sont pour l'instant au nombre de quatre.
Les noms des fonctions exportées sont sensibles à la casse (il y a une différence entre majuscule et minuscule).
Convention : La convention permet de spécifier la gestion du passage de paramètre autrement dit comment les paramètres vont être gérés. Elles sont pour l'instant au nombre de quatre.
- stdcall : qui est la convention par défaut mais il est bon de le préciser quand même (en cas d'évolution du langage). Cette convention permet d'exporter ces fonctions dans une grande majorité des langages. Elle est apparue avec les systèmes 32 bits.
- register : permet de placer les paramètres dans les registres donc d'optimiser leur vitesse d'accès.
- pascal : utilise une convention propre au Pascal.
- cdecl : Convention du c.
Par défaut nous vous conseillons d'utiliser surtout stdcall, c'est le format
le plus reconnu par tous les langages.
Si votre dll ne sera utilisée que par des programme avec Delphi, utilisez register qui est plus performant.
Si votre dll ne sera utilisée que par des programme avec Delphi, utilisez register qui est plus performant.
Si vous voulez connaitre plus en détail les différences entre les conventions,
allez ici :
Conventions
d'appel
|
Pour déclarer une convention :
fonction toto(liste de paramètre et leur type)[:retour si function]; stdcall;
fonction toto(liste de paramètre et leur type)[:retour si function]; stdcall;
Exemple avec notre dll :
-
Dans le projet dll, on ajoute la convention à la fin de la fonction. Remarquez le mot export (sans s) qui se place après la convention et notez qu'il n'est pas obligatoire.
function
Factorielle(n :integer
):integer
;stdcall
;export
;begin
if
n =0
then
Result :=1
else
Result := n * Factorielle(n-1
);end
; -
Dans le projet utilisant la dll, on rajoute la même convention
implementation
function
Factorielle(n :integer
):integer
;stdcall
;external
'
LibMaths
'
;{$
R
*.DFM
}
Importation :
- Importation par nom : on reprend la déclaration de la Dll et on ajoute
external 'nom du fichier dll sans l'extension'
function Toto(n : integer): integer; stdcall; external 'NomDLL';
On peut aussi changer le nom de la fonction sous lequel on veut la manipuler dans l'application ex :
function Machin(n : integer): integer; stdcall; external 'NomDLL' name 'Toto'
Où Toto est le nom de la fonction dans la DLL et Machin le nom de la fonction dans l'application. Attention, pas de ';' entre external et name. - Importation par index : par grand chose à dire donc un exemple
:
function Machin(n : integer): integer; stdcall; external 'NomDLL' index 10;
Manipulation des chaînes Longues : Si vous avez utilisé l'Expert Dll, vous avez pu remarquer un commentaire vous mettant en garde sur l'utilisation des chaînes longues. Cette mise garde ne concerne que la manipulation des chaînes comme paramètre, c'est à dire entrant ou sortant de la dll (en interne, pas de problème). En effet, Delphi gère les chaînes longues d'une façon qui est incompatible avec les autres langages. Vous avez lors deux solutions soit utiliser ShareMem et inclure la dll : BORLNDMM.DLL avec votre application, soit utiliser des PChar ou ShortString (voir le chapitre XVIII.B.5. DLL & Chaîne de caractère). Attention même les chaînes qui sont dans des enregistrements ou des classes sont concernées ! Voir le chapitre Chargement Statique/Dynamique pour les importations avancées. |
XVIII-B-3. DLL de Classe
Les dll permettent aussi d'exporter des classes à condition de comprendre quelques
subtilités liées au classe. Commençons par définir une classe dans un projet
dll (Nouveau-> expert dll). ajouter une nouvelle unité (Nouveau->Unité) qui
contiendra notre classe et commencons sa définitions.
|
-
J'ai déjà noté les fonctions que je voulais exporter mais les déclarer dans la partie exports ne serait pas suffisant. Nous manipulons ici une classe et non un ensemble de fonction, c'est donc la classe qu'il nous faut exporter. Voici comment procéder :
function
CreeInstanceMaClass() : TMaClass;export
;//
Pas
de
virtual
ici
!
begin
Result := TMaClass.Create;end
; -
Dans la partie exports, on exporte seulement cette fonction. On exporte la classe depuis la page principal soit celle avec le mot library
Le code complet de la page principal puis de l'unité contenant la définition de la classe ://
Page
principal
library
Dll_ClassPrj;uses
SysUtils, Classes, MaClassDLLUntin
'
MaClassDLLUnt.pas
'
;{$
R
*.res
}
exports
CreeInstanceMaClass;//
Seul
la
fonction
permettant
d'instancier(créer)
un
objet
de
la
classe
est
exporté
!
begin
end
.//
Unité
contenant
la
définition
de
la
classe
unit
MaClassDLLUnt;interface
type
TMaClass =class
private
bidon :integer
;public
function
GetBidon() :integer
;virtual
;stdcall
;export
;procedure
SetBidon(NewValeur :integer
);virtual
;stdcall
;export
;end
;function
CreeInstanceMaClass() : TMaClass;stdcall
;export
;//
Pas
de
virtual
ici
!
implementation
function
TMaClass.GetBidon():integer
;stdcall
;export
;begin
//
Renvoie
la
valeur
de
bidon
Result := Bidon;end
;procedure
TMaClass.SetBidon(NewValeur :integer
);stdcall
;export
;begin
//
Change
la
valeur
de
bidon
en
NewValeur
Bidon := NewValeur;end
;function
CreeInstanceMaClass() : TMaClass;stdcall
;export
;//
Pas
de
virtual
ici
!
begin
Result := TMaClass.Create;end
;end
.
Voilà pour la dll, nous allons créer une application l'utilisant. Contrairement à
ce qui avait été dit auparavant export ne sera pas la dernière déclaration dans
l'importation des fonctions. Il s'agit d'abstract qui terminera nos déclarations.
Pourquoi et bien parce que mais plus sérieusement, pour pouvoir utiliser une classe
il faut que l'application connaisse sa définition. C'est pourquoi nous avons déclaré
toutes nos méthodes (les fonctions quand elles sont dans une classe) virtuel et que
nous utilisons abstract ce qui permet de mettre la définition sans implémenter les
méthodes (voir les classes pour plus de précision).
Dans un nouveau projet, voici pour l'apparence :
Dans un nouveau projet, voici pour l'apparence :
Ensuite, repérez la fin de la déclaration de Form et ajouter la définition de
la classe dll. Il suffit de recopier la déclaration de la classe et d'ajouter
abstrac à la fin de chaque méthode exportée. Après les directives
de compilation ({$R *.DFM}), importez la fonction qui permet de crée une instance
de la classe. voir ci dessous le code complet.
|
XVIII-B-4. DLL de Composant
Nous allons réaliser une petite calculette sans prétention. Le but est d'apprendre
à utiliser des composants Delphi dans une DLL et de voir quelques écueils.
Commencez un nouveau projet DLL et ajoutez une fiche. Enregistrer le projet (PMadll) et l'unité (UMadll). Dans la fiche ajouter les composants et changer la propriété BorderStyle à bsDialog de la fiche, de façon à obtenir ce résultat :
Commencez un nouveau projet DLL et ajoutez une fiche. Enregistrer le projet (PMadll) et l'unité (UMadll). Dans la fiche ajouter les composants et changer la propriété BorderStyle à bsDialog de la fiche, de façon à obtenir ce résultat :
Le bouton égal est obtenu en mettant le signe '=' dans Caption et le plus par un label avec une fonte différente
Ajoutez dans la clause Uses : Forms avant UMadll.
-
Dans l'événement OnClick du bouton égal :
procedure
TMaForm.Button1Click(Sender: TObject);begin
Label3.Caption := FloatToStr(StrToFloat(Edit1.Text) + StrToFloat(Edit2.Text));end
; - J'ai renommé Form1 en MaForm pour la suite !
-
Retournez sur l'unit Projet, Il nous faut donner le moyen à l'application de créer la Form car vous remarquerez qu'il n'y a pas de création automatique contrairement aux exécutables.
procedure
Creer_Form;stdcall
;export
;begin
//
Crée
une
instance
de
Form
DecimalSeparator :='
.
'
;//
change
le
séparateur
décimal
Application.CreateForm(TMaForm, MaForm); MaForm.Label3.Caption :='
0
'
;end
; -
Comme nous créons la Form, nous allons nous donner le moyen de la libérer
procedure
Free_Form;stdcall
;export
;begin
//
Libère
la
Form
MaForm.Free;end
; -
Nous allons maintenant ajouter une fonction retournant la somme calculée.
function
fncsomme():Double
;stdcall
;export
;begin
with
MaFormdo
begin
//
Montre
la
forme
et
attend
sa
fermeture
ShowModal;//
Renvoi
le
résultat
du
Calcul
Result := StrToFloat(Label3.Caption);end
;end
; -
Et une version procédure qui ne sera utilisé que dans le chapitre autre langages.
procedure
proSomme(var
R:Double
);stdcall
;export
;begin
with
MaFormdo
begin
//
Montre
la
forme
et
attend
sa
fermeture
ShowModal;//
Modifie
la
variable
R
(passage
par
adresse)
R := StrToFloat(Label3.Caption);end
;end
; -
Exportons nos fonctions et notre DLL sera terminée
exports
Creer_Form, Free_Form, fncsomme, proSomme;
Réalisons maintenant une application qui utilisera cette dll. Enregistrer votre
projet dll si ce n'est déjà fait et commencez un nouveau projet application.
Nous allons faire simple et nous contenter d'un bouton pour appeler la calculette
et d'un label pour afficher le résultat, nous mettrons aussi la fiche en bsDialog.
L'apparence étant faite, passons au codage. Il nous faut importer les fonctions
permettant de manipuler la DLL. Donc nous allons importer les fonctions qui
permettent de créer la form, de la libérer et bien sûr d'utiliser la calculette.
Juste après les directives de compilation ( {$R *.DFM} ) ajoutez :
Juste après les directives de compilation ( {$R *.DFM} ) ajoutez :
|
- A niveau de la Function, vous aurez remarqué que j'ai changé le nom de la fonction pour Total.
-
Dans l'événement OnClick du Bouton 'Appel de la Calculette', Tapez le code suivant (fmUseDll est le nom de ma Form) :
procedure
TfmUseDLL.btAppelCalculetteClick(Sender: TObject);var
R :Double
;begin
Creer_Form; R := Total(); lbResultat.Caption := FloatToStr(R);//
Ne
pas
oublier
de
libérer
la
form
à
la
fin
Free_Form;end
; - Enregistrer votre projet et vérifiez que la DLL se trouve dans le même
répertoire que la casse des fonctions importés est correct.
Lancez votre appli, vous disposez maintenant d'une dll proposant une calculette :)
XVIII-B-5. DLL & Chaîne de caractère
Si votre dll doit utiliser des chaînes longues comme paramètre, vous allez vous
heurter à un problème. Heureusement deux solutions s'offrent à vous.
- La plus simple utiliser l'unité ShareMem en le déclarant dans la clause uses avant toutes les autres déclarations. Vous pouvez alors utiliser les chaînes longues comme bon vous semble. Cette méthode bien que simple à un gros inconvénient, pour que votre application puisse fonctionner, il lui faut alors une autre dll. Le problème et qu'il ne faut pas oublier de fournir cette dll sous peine d'empêcher l'exécution de votre application. le fichier dll à inclure : BORLNDMM.DLL.
- La plus indépendante, utilisez des PChars ou des ShortStrings. En ce qui concerne les ShortStrings, ils se manipulent comme les strings si ce n'est le nombre limit de caractère qu'ils peuvent contenir.
Attention même les chaînes qui sont dans des enregistrements ou des classes
sont concernées !
|
Je ne parlerai ici que des PChars et de leur utilisation avec les dll, le reste
ne posant pas de difficulté. Tout d'abord un PChar est une chaîne un peu spéciale
(rien que le nom est bizarre mais très révélateur). Les Pchars sont des pointeurs
sur une chaîne de Char terminé par un indicateur de fin. Une bonne nouvelle
lorsqu'on les manipule sous delphi, ils sont compatibles avec les string.
MonPChar := MonString;
Par contre, l'indicateur de fin est le caractère #0. Du coup ce caractère ne doit pas se retrouver dans la chaîne (la chaîne s'arrête dés qu'elle rencontre celui-ci). Cet indicateur de fin est aussi appelé null ou Zéro Terminal.
Le point le plus délicat est l'espace mémoire occupé par ces PChars, contrairement au string, il est fixe. Mais avant d'entrer dans le vif du sujet un exemple d'utilisation vous permettra de fixer les choses :
MonPChar := MonString;
Par contre, l'indicateur de fin est le caractère #0. Du coup ce caractère ne doit pas se retrouver dans la chaîne (la chaîne s'arrête dés qu'elle rencontre celui-ci). Cet indicateur de fin est aussi appelé null ou Zéro Terminal.
Le point le plus délicat est l'espace mémoire occupé par ces PChars, contrairement au string, il est fixe. Mais avant d'entrer dans le vif du sujet un exemple d'utilisation vous permettra de fixer les choses :
-
Soit MaFonction : une fonction qui attend une chaîne de caractère de type PChar qui la manipule et la renvoie. Le détail de la fonction n'est pas ce qui nous intéresse.
function
MaFonction(S :PChar; Taille :integer
);On ne met pas var devant S car c'est un pointeur donc une adresse (passer une adresse d'une adresse :o )
-
Dans une partie du code nous voulons utiliser cette fonction, soit :
var
U :array
[0
..51
]of
Char
;//
Déclaration
d'une
chaîne
de
caractère
begin
U :='
affectation
d
'
'
une
valeur
'
; MaFonction(U, sizeof(U)); ... Suite des instructions...end
;
Dans le passage de paramètre, on passe un pointeur sur la chaîne de Caractère
soit U par adresse et la taille de la chaîne. Quoique fasse la fonction
MaFonction, elle ne devra pas dépasser cette taille-1 (pensez au caractère
de fin de chaîne).
Pour copier une chaîne dans un Pchar en limitant la taille d'arriver, vous pouvez utiliser la fonction StrPLCopy
Pour copier une chaîne dans un Pchar en limitant la taille d'arriver, vous pouvez utiliser la fonction StrPLCopy
|
On peut trés bien se passer des tableaux de caractère en utilisant tout de suite
un PChar. Dans ce cas, il faut allouer de l'espace mémoire avant de les utiliser.
Le code précedent deviendrait :
|
Les notions vue ici sont aussi valables pour transmettre des tableaux n'étant
pas des tableaux de caractère. Il suffit de remplacer les pointeurs PChar par
un pointeur sur votre tableaux.
Exemple avec un tableau de Double :
Exemple avec un tableau de Double :
-
Dans l'application, on déclare le tableau normalement met on ne transmet que le premier élement par adresse. En transmettant le premier élément par adresse, on passe en fait un pointeur sur le tableau. On n'aurait pu utilisé un vrai pointeur.
var
Mat :array
[0
..5
]of
double
;begin
for
i:=0
to
5
do
Mat[i] :=0
; TransmettreMat(Mat[0
],5
);//
On
transmet
aussi
la
Taille
!
end
; -
Dans la DLL, la fonction attend un paramètre par adresse (le pointeur sur le tableau) et la taille.
procedure
transTableau(var
T :Double
;taille :integer
);stdcall
;export
;type
TTabMat =array
[0
..6
]of
double
; PTabMat = ^TTabMat;var
i :integer
; pMat : PTabMat;begin
//
fixe
le
séparateur
décimal
DecimalSeparator :='
.
'
;//
fait
pointer
pMat
sur
l'adresse
de
T
donc
sur
le
tableau
transmit
pMat := @T;for
i:=0
to
tailledo
pMat^[i] := pMat^[i] +1
.33
;end
;
XVIII-C. Chargement statique/dynamique
Nous avons jusqu'à présent charger les dll de façon statique. Ceci présente plusieurs
avantage, vous savez tout de suite si votre application n'arrive pas à charger votre
dll, le code est plus compact et vous ne vous posez pas de question sur le déchargement
de la dll. Par contre la dll est toute de suite chargée en mémoire et elle ne libèrera
cet espace que lorsque l'application sera terminée, si les ressources de la dll ne
sont utilisées que ponctuellement quel gaspillage. Nous allons apprendre ici à charger
les dll de façon dynamique, c'est à dire les chargé en mémoire que pour le temps de
leur utilisation effective. Un autre avantage est la création de plugin (mais je
n'en sais pour l'instant pas plus).
Voyons tout de suite les nouveaux mots clé :
D'abord, il faut charger la dll pour cela, on utilise LoadLibrary :
Voyons tout de suite les nouveaux mots clé :
D'abord, il faut charger la dll pour cela, on utilise LoadLibrary :
|
Le handle permet de pointer sur cette dll (pensez aux objets d'une classe), si le
chargement échoue le handle à une valeur nulle.
Ensuite, il faut charger chaque fonction dont on n'aura besoin grâce à GetProcAddress :
Ensuite, il faut charger chaque fonction dont on n'aura besoin grâce à GetProcAddress :
|
Où nomdelafonctiondanslaDLL est le nom de la fonction que l'on veut importer et
MaFonction un pointeur sur processus, ici on récupère son adresse ! Si le
chargement de la fonction échoue le pointeur renvoie nil.
Lorsque la dll n'est plus requise, il faut la décharger par l'appel à FreeLibrary.
Lorsque la dll n'est plus requise, il faut la décharger par l'appel à FreeLibrary.
|
Reprenez l'exemple sur la Factorielle (le projet et la dll associé). Nous allons
modifier l'application pour qu'elle charge dynamiquement la dll libMaths.
|
La déclaration de fonction a disparu et dans l'événement onclic du bouton, on charge
la dll, la fonction Factorielle et une fois le travail accomplit, on libère
la DLL.
Libération des ressources : Remarquez l'imbrication du try..finally : On essaie d'abord de charger la dll si l'opération réussit (ici traité par un if mais un try..except est tout à fait valable), on rentre dans un bloc protégé (try) même si une exception arrive la ressource sera libérée car elle est dans la partie finally. Par contre, ce serait une erreur de mettre loadlibrary dans le code protégé. |
XVIII-D. DLL et Autres Langages
Dans l'intro, vous avez pu lire que la DLL pouvait être écrite dans un langage et
utiliser dans un autre. Je ne vous présenterais pas ici des dll écrite dans un
autre langage et utilisé dans Delphi car cela à peut d'intérêt. Il vous suffit de
reprendre ce que l'on a déjà vu. Parlons plutôt du cas où la Dll a été écrite en
Delphi et l'application dans un autre langage. Une chose importante, les conventions
d'appel dans la DLL doivent être compatibles avec le langage utilisé pour
l'application. En général stdcall.
Un autre point très délicat concerne les paramètres manipuler par la dll, notamment les chaînes longues voir XVIII-B-5 DLL & Chaîne de caractère. Mais aussi le composant Menu qui pour une raison qui m'échappe ne marche qu'avec des applications Delphi.
Les précautions d'usage étant établies, essayons de réaliser deux exemples. Le premier utilisera le langage VB et le second HT Basic avec un but identique manipuler la Calculette que nous avons créée dans le chapitre 102.2.4. DLL de composant. Que vous n'ayez ni l'un ni l'autre de ces langages n'a pas vraiment d'importance, c'est exemple n'étant que didactique. L'important est de savoir comment importer les ressources contenues d'une dll dans le langage que vous utilisez.
Reprenons notre dll Calculette et copions la dans le répertoire où se retrouvera l'application en VB.
Crée un nouveau projet VB et occupons-nous de son apparence en réalisant une fiche dans ce style :
Un autre point très délicat concerne les paramètres manipuler par la dll, notamment les chaînes longues voir XVIII-B-5 DLL & Chaîne de caractère. Mais aussi le composant Menu qui pour une raison qui m'échappe ne marche qu'avec des applications Delphi.
Les précautions d'usage étant établies, essayons de réaliser deux exemples. Le premier utilisera le langage VB et le second HT Basic avec un but identique manipuler la Calculette que nous avons créée dans le chapitre 102.2.4. DLL de composant. Que vous n'ayez ni l'un ni l'autre de ces langages n'a pas vraiment d'importance, c'est exemple n'étant que didactique. L'important est de savoir comment importer les ressources contenues d'une dll dans le langage que vous utilisez.
Reprenons notre dll Calculette et copions la dans le répertoire où se retrouvera l'application en VB.
Crée un nouveau projet VB et occupons-nous de son apparence en réalisant une fiche dans ce style :
Attaquons ensuite le codage, comme pour Delphi, nous allons importer les fonctions
nécessaires à la manipulation de la DLL. Ajoutez les lignes suivantes au début de
la page de code de la fiche :
|
Idem pour le bouton 'appel de la calculette' :
|
Exécuter votre application, et voilà une calculette faite en Delphi utilisée par VB.
Ci-dessous le code source complet d'une application écrite en HT Basic utilisant la calculette écrite en Delphi. Comme je l'avais déjà dit auparavant, peu importe que vous utilisiez ce langage ou non. Notez par contre la similitude du codage dans les différents langages.
Ci-dessous le code source complet d'une application écrite en HT Basic utilisant la calculette écrite en Delphi. Comme je l'avais déjà dit auparavant, peu importe que vous utilisiez ce langage ou non. Notez par contre la similitude du codage dans les différents langages.
|
XVIII-E. Conclusion
Petite astuce de débogage : Pour explorer le code de votre dll pendant l'exécution, ouvrez votre projet dll dans exécuter choisissez paramètre et indique une application exploitant votre dll dans Application hôte. la dll se comporte alors comme si elle était exécutable, vous pouvez placer des points d'arrêt, faire du pas à pas ... |