Professional Documents
Culture Documents
Apprenez crer simplement des DLL avec Delphi ! Mes autres articles se trouvent sur Introduction I. Cration du projet
I-1. Cration du fichier I-2. "library" plutt que "program" I-3. L'avertissement de Borland
II-1. Ce qu'il faut savoir II-2. Code minimal II-2-a. Compilation et inclusion de ressources II-2-b. Dclaration et implmentation de fonctions/procdures II-2-c. Exportation des fonctions II-3. Utiliser la DLL II-3-a. Mthode statique II-3-b. Mthode dynamique
III-1. Quand la liaison statique est suffisante III-1-a. La DLL III-1-b. Le programme III-2. Lorsque la liaison dynamique s'impose III-2-a. La (les) DLL III-2-b. Le programme
Partager des types entre application et DLL Veiller aux types de variables utiliss Les conventions d'appel Dboguer une DLL
Introduction
Une DLL, ou Dynamic Link Library, est un fichier contenant du code compil la manire d'un excutable. L'utilit est toutefois ici d'exporter les fonctions ou les procdures correspondant ce code, afin de les mettre disposition de toute application sachant les exploiter. Le but est ainsi de crer un code rutilisable par diffrents programmes, quels qu'ils soient. Delphi nous offre donc la possibilit de crer des DLL et d'en utiliser, qu'elles aient t programmes avec Delphi ou un autre langage. Nous allons donc tudier la procdure suivre pour crer et utiliser ces DLL, ainsi que certaines habitudes qu'il est bon de prendre pour assurer une ventuelle compatibilit avec des programmes qui ne seraient pas dvelopps avec Delphi.
I. Cration du projet
library Project1; { Remarque importante concernant la gestion de mmoire de DLL : ShareMem doit tre la premire unit de la clause USES de votre bibliothque ET de votre projet (slectionnez Projet-Voir source) si votre DLL exporte des procdures ou des fonctions qui passent des chanes en tant que paramtres ou rsultats de fonction. Cela s'applique toutes les chanes passes de et vers votre DLL --mme celles qui sont imbriques dans des enregistrements et classes. ShareMem est l'unit d'interface pour le gestionnaire de mmoire partage BORLNDMM.DLL, qui doit tre dploy avec vos DLL. Pour viter d'utiliser BORLNDMM.DLL, passez les informations de chanes avec des paramtres PChar ou ShortString. } uses SysUtils, Classes; {$R *.res} begin end.
Dans Delphi 2005, la procdure est la mme, si ce n'est que l'item "Expert DLL" se trouve dans le dossier "Proje La validation de la bote de dialogue vous ramnera l'diteur de Delphi qui contiendra la nouvelle unit cre, dont voici le contenu : Lorsqu'on le voit pour la premire fois, ce fichier peut tre droutant. C'est pourquoi nous allons nous attacher en dcrire les diffrences avec un projet d'application "classique".
Ajouter 'ShareMem' en premire position dans la clause uses de votre DLL et des applications qui Dployer la DLL 'BORLNDMM.DLL' (Borland Memory Manager) avec votre propre DLL.
l'utiliseront Cette bibliothque de Borland remplace le gestionnaire de mmoire par dfaut de votre DLL dans le but de permettre l'change en mmoire de vos variables de type string.
Autant dire que ce n'est pas vraiment avantageux d'utiliser des string avec des DLL ! Cela limiterait d'ailleurs leur emploi des programmes incluant ShareMem, donc des programmes Delphi. Ce problme avec les string n'est pas le seul devoir tre relev, car les types de variables entre les langages ont souvent des caractristiques diffrentes. Tout cela sera discut dans la dernire partie de cet article.
Sur le site www.wotsit.org vous trouverez en recherchant le mot-clef "DLL" une srie de fichiers dcrivant le Comme bon nombre de fichiers, les DLL commencent par un entte donnant diverses informations sur le fichier, et la taille des donnes qui suivent. Celles-ci sont principalement, dans le cas du format PE, organises entables et en segments. Nous ne nous intresserons qu'aux tables des DLL, et principalement deux d'entre elles, dont une tout particulirement. La premire est plutt donne titre informatif, il s'agit de la table des ressources. Cette table liste toutes les ressources contenues dans les segments de donnes, renseignant leur nombre, le nom, la taille, le type de chacune etc.
Vous pouvez crer des DLL qui ne contiennent que des ressources de tout type, sans quelque forme de code qu fichier que Windows sera mme d'exploiter pour personnaliser vos diffrents dossiers et raccourcis. Voyez cet La seconde table intressante est la table des exportations. Comme dit plus haut, l'objectif d'une DLL est de fournir plusieurs applications une srie de fonctions ou de procdures. Ces dernires doivent donc tre accessibles qui sait les appeler, et pour cela elles sont exportes par l'intermdiaire de la table d'exportation. Cette table recense toutes les fonctions ou procdures exportes en fournissant leur
identifiant et l'adresse o les trouver dans la DLL. Les identifiants utiliss sont soit un nom sous forme d'une chane de caractres, soit un nombre unique au sein de la DLL.
La table d'exportations ne permet pas de retrouver les types des paramtres que demandent les fonctions ou Voyons maintenant comment sont agencs ces diffrents lments et, paralllement, comment Windows les utilise : En rgle gnrale, chaque excutable est li une ou plusieurs DLL, ne serait-ce que pour les appels l'API Windows. Une partie du code ncessaire aux applications est donc contenue dans diffrentes DLL, et un accs doit tre effectu chacune d'entre elles pour le bon fonctionnement du programme. Lorsqu'un excutable est lanc dans Windows, il est charg en mmoire par un PE-Loader. C'est par son intermdiaire que les liaisons avec les DLL seront effectues. Pour cela, le PE-Loader va charger les DLL ncessaires et, grce leur table d'exportations, retrouver toutes les fonctions ou procdures dont l'application a besoin. Il copiera en mmoire le code de ces routines de telle sorte que l'excutable pourra y accder au besoin, et lancera l'application. Ce fonctionnement implique trois "phases" distinctes dans la cration d'une DLL :
La compilation et l'inclusion de ressources ventuelles. La dclaration puis l'implmentation des fonctions et procdures de la DLL. La demande d'exportation de ces fonctions pour qu'elles soient mises disposition d'autres
applications. Nous allons immdiatement voir les solutions minimales pour raliser ces trois tapes.
II-2-a. Compilation et inclusion de ressources Cette premire tape n'est pas indispensable dans la ralisation d'une DLL. Qui plus est, la cration et l'inclusion de ressources dans un projet pourrait faire l'objet d'un tutoriel complet. C'est pourquoi nous ne dvelopperons pas cette partie. Vous pouvez vous rfrer cet article de DelphiCool si vous ne savez pas manipuler les ressources. Dans le code de la DLL, si vous souhaitez inclure un fichier de ressources que vous avez compil, ajoutez simplement la directive de compilation '{$R fichier_ressources.res}' pour que Delphi le prenne en compte lors de la compilation. Votre code ressemble donc ceci :
res}
La compilation de ce code, si "fichier_ressources.res" existe dans le rpertoire de votre projet, vous donnera d'ores et dj une DLL valide mais... vide de code ! Nous allons donc maintenant voir comment lui donner une quelconque utilit.
II-2-b. Dclaration et implmentation de fonctions/procdures Notre premire DLL aura pour fonction de calculer la somme de deux nombres qui lui seront passs en paramtres, en appelant la fonction Somme. Rien de bien compliqu au niveau de l'implmentation, voici donc le nouveau code :
Il n'y a pas besoin de plus ! La fonction est implmente directement avant le "begin ... end.", sans dclaration prliminaire. Seule la convention d'appel "stdcall" est inhabituelle. Elle permet de spcifier la manire dont les paramtres sont envoys la fonction. Cette convention n'est pas la seule, mais est la plus courante. Ce point sera discut en dernire partie. Maintenant que la fonction est implmente, la DLL peut tre compile sans souci, mais la fonction ne sera toujours pas accessible par un programme extrieur. C'est le but de la dernire tape.
II-2-c. Exportation des fonctions Par rapport la complexit de l'organisation d'un fichier DLL pour ce qui est de l'exportation de fonctions, Borland a su faire quelque chose de simple ct code. Les fonctions exporter doivent tre signales dans une clause "exports", en prcisant ventuellement un nom identifiant la fonction grce "name" et/ou un indice pour cette fonction grce "index". Le nom et l'indice sont facultatifs, et des valeurs par dfaut pour chacun de ces deux paramtres sont attribues s'ils ne sont pas renseigns. Dans ce premier exemple, nous ne prciserons rien. Notre fonction sera donc exporte sous le nom 'Somme', avec un indice gal 1. Voici le code complet de notre DLL :
Attention, la clause exports ne peut pas tre utilise autrement que pour des p Il est impossible d'y mentionner des variables, classes ou autres objets.
Dans la suite de cet article, le fichier compil de cette DLL sera "DLL1.dll".
Interface du programme Une fois cette interface cre, il faut lier notre application la DLL. Il y a pour cela deux mthodes : la mthode statique, la plus simple, et la mthode dynamique, plus complexe mais qui a galement son utilit. Nous utiliserons la mme interface pour les deux mthodes, seul le code changera.
II-3-a. Mthode statique La mthode de liaison statique est la plus simple mettre en place dans Delphi. Elle ne demande pas de programmation proprement parler, mais d'une simple dclaration pour chaque fonction ou procdure importer. Le principe est le suivant : vous fournissez Delphi les informations dont il a besoin pour lier votre programme la DLL, et il se charge lui-mme des oprations de liaison avec celle-ci. Ces oprations se passent au dmarrage de votre application, et apportent donc un inconvnient : si la DLL n'est pas prsente lors du lancement de l'application ou que la fonction demande n'est pas trouvable dans la DLL
donne, le programme plante et ne pourra tre excut. Il n'y a alors aucun moyen d'outrepasser le message d'erreur. Utilisez donc cette mthode en connaissance de cause et veillez ce que chaque DLL que vous utilisez soit bien dploye avec votre application. Les informations fournir Delphi pour importer votre fonction de la DLL sont les suivantes :
Le La Le Le
nom ou l'index de la fonction/procdure dans la DLL liste des paramtres, dans l'ordre, et le type de chacun type de retour s'il s'agit d'une fonction nom de la DLL o trouver la fonction/procdure
Il apparat vident qu'il faut parfaitement connatre la fonction que l'on veut utiliser pour pouvoir la lier au programme. Cette liaison se fait, comme annonc plus haut, au moyen d'une dclaration dans l'unit o vous souhaitez utiliser la fonction, dans la section "interface". Voici pour continuer notre exemple la dclaration de la fonction Somme importer de la DLL "DLL1.dll" :
rter
Comme vous pouvez le constater cette dclaration ne diffre pas normment de celles dont vous devez avoir l'habitude. Nous retrouvons la convention d'appel "stdcall". Il est important qu'elle apparaisse dans la dclaration de la fonction, et surtout que la convention d'appel utilise soit identique entre la DLL et le programme, sans quoi les erreurs seront au rendez-vous. Le mot rserv qui suit, "external", prcde le nom de la DLL qui contient la fonction importer. L'utilisation de la constante NomDLL pour stocker le nom de la DLL permet une mise jour facile sir le fichier change de nom Aprs external nous aurions pu ajouter "name" ou "index", pour spcifier un nom ou un index particulier identifiant la fonction dans la DLL. Voici ce que cela aurait donn :
fiant le nom de la fonction teger): Integer; stdcall; external NomDLL name 'Somme';
iant son index teger): Integer; stdcall; external NomDLL index 0; Pour ce qui est de notre exemple, nous n'avons pas besoin de prciser quoi que ce soit. En effet, en l'absence de "name" ou de "index", Delphi utilise le nom de la fonction dclare dans notre code comme nom d'importation. Cela correspond ici "Somme", qui est bien le nom d'exportation de notre fonction dans la DLL. Si vous vouliez utiliser "Addition" pour nom de fonction dans le code de votre application, vous seriez alors oblig de spcifier "name 'Somme'" ou "index 0" dans votre dclaration. Maintenant que la fonction est dclare, Delphi saura o aller la chercher et il ne reste plus qu' l'utiliser comme toute autre fonction dans notre code. La seule chose que nous souhaitons est que le programme affiche la somme des termes A et B lorsqu'on clique sur le bouton "A + B = ?". Il s'agit donc d'appeler notre fonction dans l'vnement OnClick de ce bouton :
meClick(Sender: TObject);
%d = %d', [A.Value, B.Value, Somme(.Value, B.Value)]); Ce qui nous donne le rsultat escompt :
II-3-b. Mthode dynamique La mthode dynamique est plus complique mettre en place, du fait qu'il faille crire quelques lignes de code pour obtenir un lien valide vers la fonction dsire. Toutefois, cette mthode offre beaucoup plus de souplesse par rapport la gestion des erreurs, de la mmoire etc. En effet cette fois, nous allons grer nous-mmes l'intgralit du processus de liaison, au moyen principalement de deux API Windows : LoadLibrary etGetProcAddress. Pour pouvoir utiliser notre fonction, il faut procder en plusieurs tapes : Dans un premier temps, demander au systme de charger notre DLL en mmoire par l'intermdiaire de l'API LoadLibrary. Ensuite, si celle-ci est bien charge, on y rcupre l'adresse de notre fonction, grce l'APIGetProcAddress. Cette adresse nous la stockons dans une variable de type function ou procedure, dont les paramtres corrects ont t renseigns. La fonction s'utilise ensuite aussi facilement qu'une autre. Voici le code illustrant ces propos, toujours avec notre mme exemple de la fonction Somme :
LL: String; var HandleDLL: THandle; NomFct: String; IndexFct: Integer = -1): Pointer;
ar dfaut
chou, on sort
se de la fonction voulue, que l'on renvoie //Si l'index est ngatif on utilise le nom sous forme de chane dress(HandleDLL, pAnsiChar(NomFct))
meClick(Sender: TObject); //Pour stocker le handle de la DLL charge (A, B: LongInt): LongInt; stdcall; //Notre fonction, sous forme de variable
teur sur notre fonction nomme 'Somme' au sein de DLL1.dll ('DLL1.dll', HandleDLL, 'Somme');
ction se passe comme avec une fonction ordinaire + %d = %d', [A.Value, B.Value, Somme(.Value, B.Value)]);
rer la DLL lorsqu'elle n'est plus ncessaire LL); un message d'erreur : appeler Somme provoquerait une violation d'accs de chargement de la fonction "Somme"'); Afin de complter les commentaires du code, voici quelques explications : La fonction LierFonction est l pour charger la DLL en mmoire, puis rcuprer et renvoyer un pointeur sur la fonction qui nous intresse. L'ide est de pouvoir rutiliser cette fonction pour n'importe quel chargement de fonction/procdure dans une DLL. Voici la description des paramtres :
DLL : Le nom du fichier DLL charger. HandleDLL : Il s'agit d'une variable que l'appelant doit pouvoir rutiliser. A la sortie de la
fonction, elle contiendra soit la valeur 0 si le chargement a chou, soit le handle de la DLL dans le cas contraire. Ce Handle doit tre utilis pour librer la DLL. NomFct : Nom de la fonction charger. Il s'agit du nom d'exportation de la fonction qui nous intresse au sein de la DLL. IndexFct : Index de la fonction charger. Par dfaut -1, l'index ne sera utilis la place du nom que si sa valeur est donne. La procdure qui suit LierFonction n'est autre que l'vnement OnClick de notre bouton "A + B = ?". Deux variables y sont dclares. La premire, de type THandle, est l pour satisfaire au paramtre var "HandleDLL" deLierFonction. La seconde est plus inhabituelle : il s'agit d'une variable de type function, dont les paramtres et la convention d'appel sont dclars l'identique de notre fonction Somme dans DLL1.dll. Cette dclaration donne Delphi les informations dont il aura besoin lorsque nous appellerons la fonction, mais du ct de la mmoire, notre variable n'est qu'un simple pointeur sur une adresse qui contient le code de la fonction excuter. Ainsi, l'adresse de la fonction rcupre par GetProcAddress peut tre assigne notre variable, ce qui nous permet d'utiliser la fonction exporte de la DLL comme nous l'aurions fait avec toute autre fonction. Lorsque le bouton "A + B = ?" est press, l'adresse de Somme est donc retrouve avec LierFonction, puis si la fonction a bien t trouve, elle est appele dans un ShowMessage, et enfin, chose ne pas oublier, la DLL est libre de la mmoire, ce qui justifie la sauvegarde dans une variable de son Handle. Comme en tmoigne la copie d'cran, le rsultat est identique la mthode statique du ct utilisateur, mais du ct de la programmation la mthode dynamique nous offre les moyens de contrler les erreurs de chargement de la DLL et de la fonction elle-mme, un niveau o il est encore possible de prvoir une raction "pense" du programme.
Cette premire DLL et l'application l'utilisant sont maintenant termines. Vous avez dsormais en main les lments pour crer vos propres bibliothques de fonctions et les exploiter bon escient. Dans la prochaine partie nous verrons comment aller plus loin avec les DLL et leur trouverons une application qui s'approche plus de quelque chose d'utile qu'une simple somme !
partie est d'implmenter une mise en oeuvre plus concrte d'une ou plusieurs DLL et de dans une application que nous dvelopperons. Nous verrons deux exemples, chacun l'une ou l'autre des mthodes statique ou dynamique. de voir plus prcisment comment choisir entre ces deux approches de liaison avec une
III-1-a. La DLL Notre DLL doit contenir deux fonctions exportes : une de sauvegarde, et une de chargement. Pour l'exemple nous implmenterons une fonction de cryptage par XOR et une autre par dcalage de caractres, l'instar du code de Csar. Rien de bien compliqu, juste de quoi faire deux mthodes diffrentes ! Nous passerons en paramtres aux fonctions de chargement/enregistrement la mthode de cryptage utiliser et la valeur entire qui lui servira. Tout d'abord crons nos deux mthodes de cryptage. Elles se rsument chacune en une ligne, qui crypte le caractre pass en paramtre soit en lui appliquant un XOR avec une valeur donne, soit en incrmentant la valeur ASCII de ce caractre d'un pas galement renseign. Voici la fonction de cryptage XOR :
actere) + Decalage);
Ces deux fonctions implmentes, nous allons les exploiter au niveau du chargement et de la sauvegarde. Commenons par cette dernire : il faut lui passer un nom de fichier enregistrer, le texte qu'il doit contenir et les informations concernant le cryptage utiliser. Pour l'enregistrement proprement dit, nous utiliserons un Stream de fichier dans lequel nous crirons le texte fourni en le cryptant, caractre par caractre. Voici le code comment de la fonction d'enregistrement :
r du contenu enregistrer
Afin de pouvoir renvoyer False en cas de problme lors de l'criture du fichier, le code a t inclus dans un bloc try..except. Comme vous le voyez, on utilise un pointeur sur caractre pour avancer dans le texte enregistrer, du fait de l'utilisation du type pChar. Cette mthode d'criture caractre par caractre n'est sans doute pas des plus optimises, mais elle a l'avantage d'tre simple, et de suffire pour cet exemple ! Le type TCryptMethod est dfini ainsi, au dbut de l'unit : = 0, cmCesar = 1); Pour la mthode de chargement, un problme apparat : nous ne savons pas l'avance la taille du buffer allouer pour contenir le texte que nous souhaitons charger. Il n'est pas question de laisser la DLL allouer un buffer de la bonne taille, car c'est l'application que doit appartenir la gestion de sa propre mmoire. Nous allons donc employer une "astuce" trs en vogue au niveau des API Windows. Tout comme la fonction d'enregistrement, nous allons demander un nom de fichier, un buffer pour contenir le texte et les informations sur la mthode de cryptage employer. L'astuce, vraiment simple mais efficace, se trouve au niveau de ce buffer : soit l'appelant a effectivement
fourni un pointeur vers un buffer allou en mmoire, auquel cas on y effectue le chargement du texte, soit l'appelant a pass nil en paramtre, et la fonction renvoie alors la taille ncessaire du buffer allouer, en se basant sur la taille du fichier qu'on lui demande de charger. Ainsi le programme appellera un premire fois la fonction avec un buffer nil pour obtenir la taille du fichier, allouera son buffer et rappellera la fonction en lui passant ce dernier pour terminer le chargement. Pour ce qui est du chargement proprement parler, nous crirons le fichier crypt en son ensemble dans le buffer, puis le dcrypterons caractre par caractre avant de rendre la main au programme appelant. Ce qui nous donne le code suivant :
then
Nos deux fonctions sont maintenant prtes (vous remarquerez pour chacune d'elles que la convention d'appel stdcall a t prcise), il reste les dclarer comme exportes pour y avoir accs partir de notre programme. Pour cela, l'ajout de ces trois lignes de code en fin d'unit suffit :
'Sauver' , 'Charger';
Compilez votre DLL, et copiez-la dans un nouveau rpertoire qui contiendra les sources de notre programme. Pour la suite de notre exemple, la DLL aura pour nom "OpFichiers.dll".
III-1-b. Le programme Nous passons donc l'implmentation du programme qui exploitera cette DLL. Commenons par l'interface. Elle doit ressembler la capture d'cran suivante, o les noms des composants utiliss dans le code figurent en rouge :
Au niveau du code, nous allons commencer par les dclarations. Tout d'abord, le type TCryptMethod, puis les deux fonctions Sauver et Charger, le tout dans la section interface de l'unit. Nous y dclarons galement une constante CRYPT_VAL que nous passerons en paramtre aux fonctions pour le XOR et le dcalage de caractre.
= 0, cmCesar = 1);
ues des fonctions de chargement/enregistrement du texte ier, Contenu : pAnsiChar; Methode: TCryptMethod; ur: Integer): Boolean; stdcall; external 'OpFichiers.dll'; ier, Destination: pAnsiChar; Methode: TCryptMethod; ur: Integer): Integer; stdcall; external 'OpFichiers.dll';
Comme prvu nous dclarons donc statiquement les deux fonctions, en prcisant comme nous l'avons vu en deuxime partie le nom de la DLL qui contient le code de chacune, et la convention d'appel utiliser. Nos fonctions peuvent maintenant tre utilises comme n'importe quelle autre fonction au sein de notre code, et nous pouvons donc passer l'implmentation du programme. Son fonctionnement est on ne peut plus simple. Le choix de la mthode de cryptage/dcryptage se fait grce au composant cbxMethode, dont la slection est prise en compte dans les OnClick des boutons de chargement et d'enregistrement. Voyons tout d'abord pour ce dernier :
2Click(Sender: TObject);
tion du fichier crer ame(Fichier, 'Fichier texte (*.txt)|*.txt', '', '', '', True) then
fonction en prvenant d'une ventuelle erreur de sauvegarde ar(Fichier), memTexte.Lines.GetText, TCryptMethod(cbxMethode.ItemIndex), CRYPT_VAL) then de sauvegarde !');
Vous remarquerez que l'on peut difficilement faire plus simple ! Aprs avoir demand un nom de fichier pour le texte enregistrer, on appelle Sauver avec les paramtres ncessaires pour la sauvegarde et le cryptage du texte contenu dans notre mmo. Il faut bien sr que l'ordre des items de cbxMethode concorde avec la dfinition du type TCryptMethod pour que tout se passe correctement. Le code de chargement n'est gure plus compliqu, si ce n'est que l'astuce nonce plus haut implique quelques lignes de code supplmentaires :
1Click(Sender: TObject);
le du buffer crer par un premier appel avec Destination = nil siChar(Fichier), nil, TCryptMethod(cbxMethode.ItemIndex), CRYPT_VAL);
le passe la fonction Charger pour y rcuprer le texte de notre fichier hier), Contenu, TCryptMethod(cbxMethode.ItemIndex), CRYPT_VAL); emo t(Contenu);
Les commentaires disent tout : dans un premier temps on demande la DLL de nous fournir la taille du buffer allouer puis, une fois la mmoire rserve, on rappelle la fonction Charger pour rcuprer le contenu du fichier et l'afficher dans notre mmo. Comme vous pouvez le voir c'est une mthode assez efficace, et le "cot" d'une ligne de code supplmentaire nous apporte la scurit de grer en interne l'allocation et la libration de la mmoire utilise pour le chargement. Ce programme est maintenant termin. Pour rsumer la situation, la liaison statique nous tait donc permise ici par notre connaissance du nombre et de la dfinition exacts des fonctions qui seraient employes. Celles-ci, implmentes dans la DLL, ont pu tre exploites directement sans autre ajout qu'une dclaration adquate pour chacune d'elle dans l'unit du programme. Cette mthode permet d'ajouter notre guise de nouvelles fonctions d'enregistrement et de chargement l'application, mais n'offre pas cependant toute la flexibilit que l'on peut attendre des DLL. Comme nous allons le voir prsent, une plus grande souplesse peut tre obtenue grce la liaison dynamique.
des
DLL
les
plugins.
Il existe de nombreuses manires de raliser un systme de gestion de plugins ; ce que nous verrons ici n'est donc pas la mthode, mais une mthode parmi tant d'autres. Voyez les rfrences pour d'autres points de vue sur le sujet. De notre ct, nous irons assez simplement. Imaginons un programme possdant un composant TMainMenu. Nous souhaitons pouvoir ajouter, grce des plugins, un nombre indfini d'items ce menu. Le programme devra donc chercher, dans un rpertoire rserv par exemple, tous les plugins qu'il est cens intgrer son menu. Devant notre mconnaissance du nombre de fichiers qui seront trouvs, et mme de la prsence ou non de plugins, nous n'avons pas le choix d'opter pour une liaison dynamique. Celle-ci permettra de s'adapter chaque situation, et d'viter toute erreur de chargement qui pourrait bloquer le fonctionnement du reste du programme.
III-2-a. La (les) DLL Vous l'aurez compris, les plugins seront des DLL. Et il va de soi qu'il faut instaurer un minimum de "norme" dans ces DLL pour que le programme puisse les reconnatre et les exploiter. Nous allons donc commencer par implmenter une fonction d'identification du plugin. Si le programme ne trouve pas cette fonction dans une DLL, il la considrera comme n'tant pas un plugin valide. Crez une nouvelle DLL et ajoutez-y la fonction suivante :
String; stdcall;
DLL n1'; Cette fonction a un double usage. Par sa prsence, elle indiquera donc au programme qu'il a bien faire un plugin qui lui est destin, et par sa valeur de retour elle donnera l'application le Caption de l'item crer dans le menu. Cet item doit ensuite recevoir des sous-items, qui donneront accs aux fonctionnalits que propose le plugin. La liste de ces sous-items doit tre donne par la DLL, de mme que les fonctions qu'ils vont appeler lors d'unOnClick doivent tre indiques par la DLL et stockes dans celle-ci. Nous donnerons en fait au programme le nom d'exportation de chacune des fonctions, ce qui lui permettra de faire les chargements ncessaires lors du dclenchement de l'vnement OnClick. Nous allons donc crer une fonction qui se chargera de fournir ces informations au programme. Par souci de commodit la fonction renverra un tableau de records listant les couples Item/Fonction, ce qui est la meilleure manire de tout renvoyer d'un seul coup. Nous allons donc dfinir deux types : le type TItem, un record de deux lments et le type TItemsList, un tableau dynamique de TItem. Afin de pouvoir aisment rutiliser ces types dans notre programme, nous allons les dclarer dans une unit part. Crez une nouvelle unit nomme par exemple "PlugUtils.pas" et dfinissez-y nos deux types, dans la partie interface :
ng; ng; TItem; Il nous faut quelque part stocker la liste des items du menu crer. Revenez l'unit de la DLL, et dclarez un tableau constant qui contiendra les Caption des items de notre plugin. Pour l'exemple, nous en crerons trois :
0..2]Of ShortString = ('Item1', 'Item2', 'Item3'); Cette liste tablie, il reste pouvoir la communiquer au programme hte de notre plugin. Nous allons donc crer une fonction GetItemsList qui renverra une tableau de type TItemsList contenant chacun des lments de notre tableau ItemsCaptions associ au nom de la fonction qui lui correspond. Afin de ne pas procder au cas par cas, nous nommerons toutes les fonctions de la mme manire, en prfixant le nom de l'item de la chane "Exec". Voici le code de la fonction GetItemsList :
ous-items crer
TItemsList; stdcall;
du tableau gth(ItemsCaptions));
tions) to High(ItemsCaptions) do
Le fonctionnement de cette fonction est tel qu'il a t dcrit : pour chaque item de ItemsCaptions, on renseigne un nouvel lment du tableau de retour en lui donnant le Caption de l'item et la fonction qui lui est associe dans la DLL. La dernire tape est maintenant de crer ces fonctions, afin qu'un click sur l'un des items puisse tre effectif. Nous ne serons pas trop originaux ici : chaque fonction ne fera qu'afficher un message texte avec un bouton "OK". Pour ne pas alourdir notre DLL, nous utiliserons la fonction MessageBox de l'unit "Windows.pas" plutt queShowMessage. Nous avons donc trois fonctions crer, dont voici les implmentations :
der: TObject); stdcall; 'Vous avez cliqu sur le premier item !!', 'ExecItem1', MB_OK);
der: TObject); stdcall; 'Vous avez cliqu sur le second item !!', 'ExecItem2', MB_OK);
der: TObject); stdcall; 'Vous avez cliqu sur le troisime item !!', 'ExecItem3', MB_OK); Le paramtre de ces procdures n'est prsent que pour des raisons de compatibilit avec le type TNotifyEvent, qui est le type d'vnement de base, et plus particulirement le type de l'vnement OnClick auquel nous associerons nos procdures. Pour finir, il reste une chose essentielle pour que la boucle soit boucle : l'exportation de toutes ces fonctions. Vous avez dj vu comment cela se passait, le code suivant n'a donc pas besoin d'explication supplmentaire :
in
'GetName', 'GetItemsList', 'ExecItem1', 'ExecItem2', 'ExecItem3'; A noter que l'exportation avec des noms corrects est ici trs importante, puisque c'est grce eux que le programme identifiera et utilisera correctement le plugin. Justement, voyons maintenant comment cela se passe du ct de l'application. Compilez la DLL et copiez la dans un sous-dossier "plugins" du rpertoire qui recevra les sources de notre nouveau programme.
Le menu "Fichier" ne contient qu'un item "Quitter". La ListBox est l pour afficher des informations sur l'avancement du chargement des plugins. Ce chargement sera fait ds le lancement de l'application, ainsi c'est dans l'vnement OnCreate de notre fiche que nous allons procder l'inspection du dossier "plugins" et l'ajout des diffrents items notre menu. Lors du chargement, le handle de chacune des DLL lies sera ajout dans un tableau dynamique afin d'tre accessible par la suite. Ce tableau est dclar de manire globale dans l'unit :
ndle; Chaque item conservera les informations qui lui sont propres. Afin de ne pas surcharger le code de tableaux en tous genres, nous stockerons le nom de la fonction lui servant d'vnement OnClick dans sa proprit Hint, et l'index du handle de la DLL o trouver cette fonction dans sa proprit Tag. A chacun des items, leur cration, nous assignerons un mme vnement OnClick qui se chargera d'utiliser ces deux informations pour lier et appeler la fonction adquate. L'vnement OnCreate ne contient que trs peu de code en lui-mme. En effet cause du fonctionnement de FindFirst et FindNext, le code de liaison des DLL et d'ajout des items est dcentralis dans une procdureAjoutMenu pour viter que de longues portions de code ne soient rptes inutilement. Cette procdure est imbrique dans la procdure OnCreate, afin de pouvoir rutiliser les variables de celle-ci. Voici dans un premier temps l'vnement OnCreate sans la procdure AjoutMenu :
eate(Sender: TObject);
re ici *}
ers DLL prsents dans le rpertoire "/plugins" ilePath(ParamStr(0)) + '\plugins\*.dll', faAnyFile, SR) = 0 then
re DLL
Le fonctionnement avec une boucle while sur FindNext conditionne par le rsultat de FindFirst ne doit pas tre nouveau pour vous ; c'est la mthode fournie dans l'aide de Delphi sur ces deux fonctions de recherche. Ainsi si un premier fichier est trouv, nous commenons par l'ajouter au menu. Le paramtre de AjoutMenu, comme nous le verrons, spcifie l'index o stocker le handle de cette nouvelle DLL dans le tableau Plugins. La valeur 0 correspond donc bien la premire DLL trouve. Ensuite chaque fois qu'une nouvelle DLL est trouve, nous l'ajoutons au menu en spcifiant comme index la taille actuelle de Plugins incrmente de 1. La procdure AjoutMenu se charge avec cette information et les autres variables de OnCreate de lier la DLL au programme et d'en utiliser les fonctions pour ajouter les items et sous-items au menu. En voici le code, qu'il faut insrer aprs la dclaration des variables de OnCreate :
ndex: Integer);
nuItem; st; rcuprer dans la DLL : ion: ShortString; stdcall; ion: TItemsList; stdcall;
tableau de DLL Index + 1); le handle de notre DLL charge ; it en liant la fonction GetName onction(ExtractFilePath(ParamStr(0)) + '\plugins\' + SR.Name, Plugins[Index], 'GetName');
a DLL si besoin <> 0 then gins[Index]); leau de DLL la bonne taille , Index); s la ListBox 'La DLL "' + SR.Name + '" n''est pas un plugin valide.')
Box du nouveau plugin trouv 'Nouveau plugin "' + PluginName + '" dans la DLL "' + SR.Name +'"');
nuItem.Create(Menu); n := ItemsList[i].Nom; s Hint le nom de la fonction appeler pour l'excution de l'item := ItemsList[i].Fonction; 'index de la DLL contenant la fonction de notre item := Index; qui gre l'vnement OnClick k := ExecItem; -item au menu em); e cet ajout d('---> Ajout de l''item "' + SousItem.Caption + '"');
Tout d'abord afin de pouvoir utiliser le type TItemsList, ajoutez dans la clause uses l'unit "PlugUtils.pas" que nous avons cre lors de l'implmentation de la DLL. AjoutMenu est peut-tre la fonction la plus complexe de cet article, mais elle surtout est plus "massive" que complique. Tout d'abord Plugins voit sa taille incrmente d'un lment, pour recevoir le handle de la DLL, et l'lment nouvellement cr est initialis 0 pour les besoins de la fonction LierFonction. Pour rappel nous avons vu cette fonction dans le troisime paragraphe de la seconde partie de cet article. Vous pouvez ventuellement le consulter pour bien comprendre le droulement du reste du code. Il faut bien sr l'ajouter au code de notre programme. Nous appelons donc LierFonction pour en rcuprer le rsultat dans la variable PluginName. Dans le meilleur des cas, elle pointera alors sur la fonction GetName exporte par la DLL que nous souhaitons ajouter. Sinon, elle contiendra nil, ce qui nous indiquera que la DLL que nous tentons d'ajouter n'est pas un plugin valide. C'est ce distinguo que nous faisons juste ensuite pour viter toute violation d'accs. Si PluginName contient nil, la liaison avec la DLL est libre et la taille de Plugins est dcrmente afin de supprimer l'item qui tait rserv pour cette dernire. Dans le cas contraire, on continue en crant un nouvel item dans le menu dont le Caption est le rsultat de l'appel PluginName. Ensuite on procde avec GetItemsList comme pour la liaison avec GetName, et chaque sous-item est ajout au menu grce la liste rcupre dans ItemsList. C'est cet instant que l'on stocke les informations concernant l'action de chacun de ces items, et que l'on associe leur vnement OnClick la procdure ExecItem. Elle constitue l'vnement OnClick "central", qui se chargera d'appeler en fonction du Sender la fonction qu'il faut dans la bonne DLL. Elle utilisera pour cela notre fonction LierFonction en lui passant en paramtres les informations auparavant stockes dans les proprits Hint et Tag de l'item.
em(Sender: TObject);
tion associe l'item dans la DLL m(Sender); tion('', Plugins[Item.Tag], Item.Hint); fonction
Pour l'appel de Lierfonction, notez que puisque Plugins[Item.Tag] est un handle valide de DLL, il n'y a pas besoin de fournir le chemin d'un fichier DLL charger. N'oubliez pas de dclarer ExecItem dans la partie Public de TForm1 pour qu'elle puisse tre utilis comme vnement des Items Le code de chargement des plugins est maintenant termin. Il ne reste qu' librer les DLL la fermeture du programme pour parfaire le tout. Il suffit pour cela de grer l'vnement OnClose de la fiche, en implmentant le code suivant :
to High(Plugins) do [i]); Le programme est maintenant bel et bien termin. Vous pouvez le tester en dupliquant autant de fois que vous le dsirez la DLL que nous avons compile prcdemment, les menus s'ajouteront automatiquement et pour chaque item vous aurez le message correspondant. Cette mthode de liaison dynamique est donc trs puissante, car elle offre une grande flexibilit tout programme qui l'utilise et permet ainsi de grer au mieux toute sorte de modules, de plugins etc. Comme vous l'avez peut-tre remarqu il y a toutefois certaines choses auxquelles il faut faire attention pour viter diverses problmes. C'est ce que nous allons traiter dans la dernire partie de cet article.
problmes qui pourraient arriver dans le cas d'un oubli de rpercussion d'une modification entre deux fichiers, par exemple.
Shortint : 8 bits sign Smallint : 16 bits sign Longint : 32 bits sign Int64 : 64 bits sign Byte : 8 bits non sign Word : 16 bits non sign Longword : 32 bits non sign
Dans le mme registre, attardons nous sur le type string. Vous vous souvenez sans doute de l'avertissement de Borland. Pour donner plus de prcisions, "borlndmm.dll" est une DLL qui sert remplacer le gestionnaire de mmoire de Windows par dfaut. Cela parce que le type string n'est pas du tout un type standard, et que pour le grer Borland a donc eu besoin de fournir un moyen personnalis. Cependant leur solution est plutt contraignante, car elle limite le fonctionnement de ces dernires des applications Delphi qui utilisent l'unit "ShareMem.pas". Il est donc prfrable, pour s'affranchir de cette contrainte, de n'utiliser dans les paramtres des fonctions de vos DLL que des types pChar ou ShortString, comme nous l'avons fait dans tous les codes de cet article. Notez bien qu'il ne vous est pas du tout interdit d'utiliser le type string au sein de votre DLL. L'important est de ne pas l'utiliser dans les changes entre votre DLL et le programme qui l'emploie.
Si vous souhaitez une alternative ShareMem et l'utilisation de Borlndmm.dll, il existe Cette unit a pour but de remplacer Sharemem.pas en bien des points : allocations de mmoire plus rapides, un
Les diffrences qu'elles apportent sur le passage des paramtres une fonction ou une procdure se jouent trois niveaux :
L'ordre dans lequel sont empils les paramtres en mmoire A qui revient la responsabilit (entre l'appelant et l'appele) de librer la mmoire occupe par Le passage ou non des paramtres par les registres processeur
La convention register est la seule passer les paramtres par les registres processeur. Dans le cas o il y a plus de trois paramtres, le quatrime et les suivants seront empils en mmoire dans l'ordre de leur dclaration. Cette convention, qui est celle utilise par dfaut lorsque les optimisations sont actives, remplace la convention pascal. Celle-ci n'existe encore que par souci de compatibilit. Elle est la convention par dfaut au cas o les optimisations ne sont pas actives. Dans les deux cas, c'est la routine appele que revient la tche de librer la mmoire alloue aux paramtres. La convention stdcall (Standard Call) est la convention principalement utilise par Windows, dans toutes ses DLL et donc notamment dans les DLL contenant les fonctions de l'API. Cela en fait la convention la plus "standard" des cinq, et c'est pourquoi nous l'utilisons pour communiquer entre nos DLL et nos programmes. Lors d'un appel une routine dclare avec stdcall, les paramtres sont empils en mmoire dans l'ordre inverse de leur dclaration, et c'est la routine qui doit librer la mmoire alloue pour ces derniers. La convention cdecl est la convention des programmes et librairies crits en C et C++. Elle est prcise par Borland comme tant moins efficace que stdcall. Avec cette convention, les paramtres sont galement empils en mmoire dans l'ordre inverse de leur dclaration, mais c'est au programme appelant qu'il revient la responsabilit de librer la mmoire qui leur est rserve. Enfin, la convention safecall est identique par ses caractristiques stdcall. Elle est utilise dans le cadre des objets Automation et sert mettre en place les notifications d'erreurs COM interprocessus, selon l'aide en ligne de Delphi. Pour conclure sur les conventions d'appel, la principale chose que vous devez garder l'esprit lorsque vous dveloppez et/ou exploitez des DLL, c'est qu'il est impratif que les fonctions soient dclares et appeles avec des conventions d'appel strictement identiques. Sans cela, vous risquez bon nombre d'erreurs, plantages ou rsultats hasardeux. Si vous souhaitez une illustration de ce qu'on peut appeler "rsultat hasardeux", essayez avec le programme Somme de la premire partie de dclarer la fonction importe en register par exemple, en gardant la DLL d'origine, puis faites effectuer au programme la somme "0 + 0" ! les paramtres
Paramtres d'excution Ici, renseignez le chemin de votre application dans le champ "Application hte" et d'ventuels paramtres pour celle-ci. Si vous voulez utilisez un rpertoire de travail diffrent de celui de votre application, vous pouvez galement l'indiquer. Une fois cette fentre valide, vous pourrez lancer la commande "Excuter" et utiliser les fonctionnalits de dbogage dans votre DLL. L'onglet "Distant" permet d'utiliser le dbogage distance grce au serveur de dbogage distance de Borland. Cette option ne sera pas aborde ici.
Conclusion
C'est maintenant la fin de cet article. J'espre qu'il vous a permis de bien cerner comment se crent et s'emploient les DLL avec Delphi, et que les exemples de la troisime partie vous ont convaincu de leur efficacit dynamiser vos applications. Je suis ouvert toute remarque, suggestion ou critique constructive quant cet article. Il vous suffit de m'envoyer un message priv par le biais du forum. Vous pouvez tlcharger les sources des exemples de la troisime partie ces adresses : Exemple de la mthode statique Exemple de la mthode dynamique Si ces liens ne fonctionnent Exemple de Exemple de la mthode dynamique pas la chez vous, utilisez mthode ces miroirs : statique
Remerciements
Merci beaucoup Delphiprog, Laurent Dardenne, LLB, Mac LAK et Nono40 pour leurs remarques et leur aide. Merci galement N*M*B qui m'a signal une erreur dans les fichiers tlcharger et une optimisation de mon code.
Rfrences
Conventions d'appel - Dtails sur les conventions d'appel dans l'article Utilisation de l'assembleur en ligne avec Delphi, par Nono40
Raliser un plug-in comportant un composant Deux mthodes avances pour raliser des plugins, par Clorish et sjrd
Le livre Delphi 7 Studio d'Olivier Dahan et Paul Toth Tout un chapitre sur les DLL, un systme de plugins et des astuces !
Utilisez les ressources dans votre application Delphi - Un tutoriel de Delphicool pour apprendre manipuler les ressources
Iczelion's Win32 Assembly Homepage - Les pages d'Iczelion, o on trouve entre autre une trs bonne documentation du format PE
formats de fichiers.
Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2005 Olivier Lance. Aucune reproduction, mme partielle, ne peut tre faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu' trois ans de prison et jusqu' 300 000 de dommages et intrts. Cette page est dpose.
Developpez.com
Nous contacter Participez Informations lgales
Services
Forum Delphi Blogs Hbergement
Partenaires
Hbergement Web Copyright 2000-2012 - www.developpez.com