Utilisation des dll -free
pascal- Par Clandestino
/ Fighting For Fun
type de sujet : coding
Niveau requis
: |
X
|
X
|
X
|
X
|
X
|
Beginner |
Un
tutoriel sur le meme sujet a ete ecrit pour les utilisateurs de Delphi
Introduction
Le
but de ce tutoriel est de vous familiariser avec l'utilisation des
dlls, mais du point de vue du programmeur. Des exemples concrets
valant mieux, a mon sens, que de longs discours,ce tut est donc surtout
compose de codes sources largement commentes pour en faciliter l'etude. Les
codes sources de ce tutoriels ont ete ecrits pour etre compiles
avec Free Pascal
, a travers une interface de developpement gratuite elle aussi :
devpascal...
Il
y a peu de tutoriels sur le sujet, aussi j'espere que le savoir que je partage ici
sera utile a beaucoup.
|
Creation d'une dll
Avec fpc, le code source d'une dll est similaire à celui d'un programme, c'est un fichier
texte avec l'extension *.pp. La principale différence réside dans le mot clef
de début de code source : Library au lieu de Program. La structure type
est la suivante : |
Library nom_de_la_dll
; { Le mot clef library indique au compilateur
de compiler une dll au lieu d'un exe. Il est suivi du nom_de_la_dll
qui sera le nom de la dll finale. }
Uses Unit1, Unit2 ; {
le mot clef Uses sera suivi des unites (*.pas ou *.dcu) que vous
voulez integrer pour la compilation... Par exemple : Uses
windows, Msg ; }
{ $R
nom_fichier_ressource.res }
{ Utilisez cette syntaxe pour integrer des resources precompilees (*.res)
a votre dll }
Var Variable1
: Type1 ; Variable2 : Type2
; { Definissez vos variables apres le
mot clef Var ou vos constantes apres le mot clef Const
}
Procedure Nom_de_la_Procedure(var1
: Type1, var2 : Type2...); stdcall ; begin {
corps de la procedure } end; {
Definissez vos Procedure
de maniere classique sans oublier de specifier la convention d'appel de dll utilisée
: ici stdcall (la convention utilisee par les dlls de windows) pour etre compatible avec C++ ou l'asm...
Explication : par defaut delphi utilise la convention register qui
(entre autre differences) passe les parametres dans l'ordre inverse des autres conventions... Pour
creer des dll compatibles avec d'autres langages, choisissez
stdcall }
Function Nom_de_la_Fonction(var1
: Type1, var2 : Type2...): type3; stdcall ; begin
{ corps de la fonction } result := ...
{ n'oubliez pas d'attribuer un resultat (ici de type3) a votre fonction
} end; {
Definissez vos fonctions de
maniere classique sans oublier de specifier la convention d'appel comme pour les
procedures }
Exports Nom_de_la_procedure, Nom_de_la_fonction; {
Precisez
apres le mot clef exports le nom des fonctions et des procedures
exportees par votre dll. }
begin
{ corps de la dll : le code placé ici sera utilise lors de l'initialisation
de cette derniere... } end.
|
Conseils importants : - Le compilateur
de freepascal ne supporte pas les chemins trop longs, les noms
de plus de huit lettres et les espaces ( comme le DOS :) )... Installez
donc devpas dans c:\devpas et travaillez dans un repertoire c:\projets.
Choisissez des noms courts et evitez les espaces. - Choisissez un nom de dll personalisé (distinct des noms
de dll utilisées par windows) car un meme programme ne peut charger deux
dll ayant le meme nom. - Soyez vigilents sur l'utilisation de la memoire
: n'oubliez pas de desalouer cette derniere par votre dll
si celle-ci l'a allouee.
|
Exemple de dll vide
Creez un nouveau code source dllvide.pp
dans devpascal et copiez-y le code suivant.
|
Library dllvide
; Uses windows ; begin
Messagebox(0,'dll
chargee en memoire','Message',MB_OK) ; {
Ce message s'affichera lors du chargement de la dll } end.
|
Enregistrez les modifications dans un dossier
de votre choix puis compilez en choisissant le bouton ad hoc ou bien.le
raccourci clavier ctrl+F9 Le dossier contient votre premiere
dll vide...
|
Exemple de Dll de ressources :
Nous allons creer une dll contenant une image. Pour
cela nous allons precompiler un fichier *.o contenant cette ressource,
puis nous inclurons ce *.o dans la compilation de notre code source
principal...
Creation d'un fichier de resource
precompile : Picture.o :
Creation tout a la main: Creez un dossier vide et creez-y un fichier texte nomme
Picture.rc.
Copiez
dans ce dossier la bitmap qui sera contenue en tant que ressource et nommez-la
image.bmp. NB J'ai utilise dans mon exemple une image de 202x76 pixels enregistre
avec photoshop en mode 8 bits / couleurs indexees pour gagner en
taille...
La conception des fichiers .rc peut faire l'objet
d'un tutoriel a elle seule. La syntaxe pour les Bitmaps, curseurs,
fonts et ressources simples est: nom_de_la_ressource TYPE_DE_RESSOURCE
nom_du_fichier.
Code source de Picture.rc
|
Compilation 'a la main' d'un fichier resource: Copiez
dans votre dossier les fichier windres.exe, gcc.exe et cpp.exe contenus dans le repertoire 'bin'
de votre devpascal puis tapez dans une console ouverte dans ce dossier la commande
: windres Picture.rc Picture.o.
Creation
avec devpascal Creez un dossier vide (prenez un chemin court
avec des noms de moins de 8 lettres pour eviter les erreurs de compilation,
exemple c:\projets) et copiez
y la bitmap qui sera contenue en tant que ressource ; nommez-la IMAGE.bmp. Choisissez le menu File > new resource file pour ouvrir
la fenetre de creation de resource integree. Si elle contient du
texte effacez le... Choisissez le bouton 'save resource file' et
enregistrez ce fichier sous le nom de picture.rc dans c:\projets... Choisissez
le bouton insert bitmap resource remplissez le comme suit :
Cliquez sur ok puis sur build resource
Le dossier c:\projets contient desormais
picture.o
Creation de la dll de ressources qui va contenir
cette image Choisissez le menu file > new source file et enregistrez
le dans c:\projets sous le nom dllres.pp Le code est simplissime, il suffit d'indiquer au compilateur
qu'il faut inclure le fichier Picture.o
Code source de dllres.pp
|
library dllres
; // choisir un nom de moins de 8 lettres {$R Picture.o}
begin
end.
|
Il ne reste plus qu'a compiler cette
dll...
|
Exemple simple de programme utilisant cette
Dll de ressources :
Nous allons creer une fiche qui va charger dynamiquement
l'image contenue dans la dll que nous venons de compiler, le resultat final
devant resembler a cela :
Je parts du principe
que vous savez creer une interface avec l'API windows... Un cours
sur ce sujet (niveau newbies) sera publié... Creez un fichier texte nomme
fenbitmap.pp, et codez-y une fenetre classique windows. Nous ne gererons pour
cet exempleque les messages WM_CREATE et WM_DESTROY Pour appeler dynamiquement
une dll il faut : - la charger en memoire grace à la commande
loadlibrary('Nom_de_la_dll') - charger les ressources
incluses grace à une commande appropriée : loadbitmap(handle_de_la_dll,'nom_de_la_ressource')
ou bien loadicon, loadcursor, loadstring, loadressource...
Voir l'aide de l'API windows pour plus de details... - trouver
l'adresse d'une procedure ou d'une fonction pour l'utiliser grace à
la commande GetProcAddress(handle_de_la_dll, 'nom_de_la_fonction')
- Libérer la mémoire avec freelibrary(handle_de_la_dll)
Code source de fenbitmap.pp
|
Program fenbmp; {$APPTYPE
GUI} {On indique au compilateur
que c'est une application avec interface graphique, sinon il tente
de creer une application en mode console...} Uses Windows;
{ On indique ici les unites utilisees pour la compilation } Var
{
les variables } WinClass:
TWndClassA; Inst: HINST; hWindow:
HWND; Image1 :
HWND; TheMessage: TMsg; HandleDll
: hwnd; himage
: hbitmap;
// GESTION DES MESSAGES function
WindowProc(hWindow: HWnd; Msg,wParam,lParam: Integer): Integer; stdcall; begin
Result := 0; case Msg of
{ Avant la
creation de la fiche } WM_CREATE
: begin {
chargement de la dll } HandleDll
:= Loadlibrary('DllRes.dll')
; {
en cas d'echec de chargement } if
HandleDll = 0 then begin messagebox(0,'Dll
non trouvée','Attention', MB_OK) ; Postquitmessage(0); end
{
sinon } else
begin himage
:= loadbitmap(handledll,pchar('MonImage'));
{ chargement de l'image }
{
on cree une image vide sur notre fenetre window } Image1
:= CreateWindow('Static','',
WS_VISIBLE or WS_CHILD or SS_BITMAP, 32,18,
// coordonnees / droite et
haut 202,76,
// largeur hauteur
hWindow,
0, Inst, nil); {
on y applique l'image chargee } SendMessage(Image1,STM_SETIMAGE,IMAGE_BITMAP,himage); end;
{
On libere la dll } FreeLibrary(HandleDll); end;
{ Avant la
destruction de la fiche }
WM_DESTROY : begin deleteobject(himage)
; { destruction de l'iimage } postquitmessage(0)
; { fermeture propre }
end; ELSE Result
:= DefWindowProc(hWindow, Msg, wParam, lParam); end; end;
// PROGRAMME PRINCIPAL begin {
enregistrement WndClass } Inst
:= hInstance; with WinClass do begin style
:=
CS_CLASSDC or CS_PARENTDC; lpfnWndProc
:= @WindowProc; hInstance
:=
Inst; hbrBackground :=
color_btnface + 1; { Astuce : pour une
couleur personnalisee du fond utiliser createsolidbrush(RGB(0,0,0))
avec les chiffres de votre choix } lpszClassname
:= 'MyWindowClass'; hCursor
:=
LoadCursor(0, IDC_ARROW); end; {
du with }
RegisterClass(WinClass);
{ Creation de la fenetre } {
utilisez l'aide sdk window incluse avec delphi si vous voulez des détails sur
les differents styles disponibles avec createwindowex } hWindow
:= CreateWindowEx(WS_EX_DLGMODALFRAME, {
style general de la fenetre (non redimensionnable)} 'MyWindowClass',
{
le nom de votre wndclass } 'Image
dans dll', //
titre de la fenetre WS_VISIBLE
or WS_CAPTION or WS_SYSMENU, {
styles particuliers de la fenetre (visible,titre et bouton)} (getsystemmetrics(SM_CXSCREEN)
DIV 2)-139,
{ distance/gauche
de votre ecran ici on centre la fiche en prenant le milieu de l'ecran
moins la demi largeur } (getsystemmetrics(SM_CXSCREEN)
DIV 2)-70,
{ distance /haut
de votre ecran } 278,
{
largeur de votre fenetre (proportion choisie pour une bitmap de 202 pixels)
} 140,
{
hauteur de votre fenetre (pour une bitmap de 76 pixels) } 0,
{
handle de la fenetre parente : ici laisser 0 } 0,
{
handle du menu ou d'une fenetre fille : ici laisser 0 } Inst,
{
handle de cette instance de l'application } nil);
{
pointeur vers les datas de création de cette fenetre laisser nil } UpdateWindow(hWindow);
{La boucle principale} while
GetMessage(TheMessage,0,0,0) do begin if
not IsDialogMessage(hWindow,TheMessage) then begin TranslateMessage(TheMessage); DispatchMessage(TheMessage); end; end; end.
|
Compilez cet exemple dans devpascal et lancez-le
pour le tester...
|
Exemple de Dll de fonction :
Nous allons creer une dll contenant une fonction. J'ai
choisi une fonction de génération de clef (celle d'un exercice du REA)
Code source de madllfonction.dpr
|
library madllfonction; {
le nom de notre dll, je choisi expres un nom trop long !!! } {
pas besoin de spécifier d'uses ni de variables globales
pour cette dll }
function calculserial(name :pchar):pchar; stdcall;
{ La fonction que l'on
veut exporter } {
je n'entrerai pas dans les détails pour expliquer le fonctionnement
de cet algorithme simplissime, ce n'est pas le but de ce tutoriel...
disons simplement que cette fonction prend un pchar, fait quelques
opérations dessus, et renvoie le resultat sous forme de pchar }
var
{
les variables utilisees par la fonction } b,i
: integer ; s, texte : string ; begin {
debut } texte
:= '' ; s := name ; getmem(result,
11) ; for i:= 1 to length(s) do begin if i<11 then begin b
:= ord(s[i]); b := b mod 10 ; b
:= b XOR (i-1) ; b := b+2 ; if b>9 then b
:= b-10 ; b := b+50 ; if b>57 then b
:= b-10 ; Texte := texte+chr(b)
; end ; end ; Texte
:= Texte+#0 ; result := @Texte[1]
; { le resultat sous forme
de pchar} end
; { fin de la fonction }
Exports
{ la partie qui stipule les
exports } calculserial;
begin {
code au chargement de la dll, ici rien... } end.
|
Ayant choisi un nom trop long, la compilation donne une dll nommee
madllf~1.dll, a moi de la renommer a la main madllfonction.dll...
|
Utilisation dynamique de cette
Dll de fonction :
Le terme dynamique, ici, designe le
fait d'appeler la dll pendant que le programme fonctionne, a un instant
precis, et de la refermer immediatement apres utilisation. Pour illustrer
cette technique nous allons creer une fiche contenant deux champs
de saisie et un bouton. Lorsqu'on appuiera sur le bouton, on chargera
la dll (et sa fonction) pour calculer à partir de la valeur du champ
de saisie1, le serial généré que l'on placera dans le champs de
saisie2. En bref : nous allons creer un keygen avec son algorithme
dans une dll...
Code source de MonGenerateur.pp
|
Program KeyGen; {$APPTYPE
GUI} Uses Windows; Var WinClass: TWndClassA; Inst: HINST; hWindow, memo1, memo2, button1: HWND;
{ handles : fenetre,
champ1, champ2, bouton } TheMessage: TMsg; HandleDll : hwnd;
{ handle de la dll } calculserial : function (name:pchar): pchar;stdcall;
{ procedure
liee au click sur le bouton} procedure click;
var nom : pchar; longueurtexte : integer; begin { Je
charge ma dll } HandleDll := Loadlibrary('madllfonction.dll'); { en
cas d'erreur :} if HandleDll = 0 then begin messagebox(0,'Attention','Dll
non trouvée', MB_OK); Postquitmessage(0); end; longueurtexte := GetWindowTextLength(memo1); { Si
on a ecrit quelque chose ...}
if longueurtexte > 0 then begin GetMem(nom, longueurtexte
+1); { memoire pour
le nom } GetWindowText(memo1,
nom, longueurtexte +1); { recup
du nom en memoire } Pointer(calculserial)
:= GetProcAddress(handledll,'calculserial');
{ recup de l'adresse
de la fonction } setwindowtext(memo2,calculserial(nom)); { affichage
du resultat dans le champ de saisie 2 } FreeLibrary(HandleDll);
{ liberation de
la dll } freemem(nom);
{ liberation de
la memoire (du nom) } end
{ Sinon
} else setwindowtext(memo2,'');
{ on efface le
champ de saisie 2 } end;
{ gestion
des messages : interception } function WindowProc(hWindow: HWnd; Message,wParam,lParam: Integer): Integer;
stdcall; begin Result := 0; case Message of { en
cas de fermeture } WM_DESTROY: begin postquitmessage(0); end; { En
cas de
commande } WM_COMMAND: begin if (lParam = Button1) then click;
{ Si la commande
vient du bouton, j'appelle la procedure click } end; { Autres
cas } ELSE Result := DefWindowProc(hWindow, Message, wParam,
lParam);
end; end;
begin Inst := hInstance; with WinClass do begin style
:=
CS_CLASSDC or CS_PARENTDC; lpfnWndProc :=
@WindowProc; hInstance :=
Inst; hbrBackground := color_btnface
+ 1; lpszClassname := 'MyWindowClass'; hCursor :=
LoadCursor(0, IDC_ARROW); end; RegisterClass(WinClass);
{ Creation de la fenetre } hWindow := CreateWindowEx(WS_EX_DLGMODALFRAME,'MyWindowClass','Fonction
dans dll', WS_VISIBLE
or WS_CAPTION or WS_SYSMENU, (getsystemmetrics(SM_CXSCREEN)
DIV 2)-130, (getsystemmetrics(SM_CXSCREEN)
DIV 2)-60, 160,120,0,0,Inst,nil);
{ Creation du champ
de saisie1 } Memo1:= CreateWindowEx(WS_EX_CLIENTEDGE, 'Edit','', WS_CHILD
or WS_VISIBLE or WS_BORDER or
ES_LEFT,5,5,140,25, hWindow, 0, Inst, nil); { Creation du
champ de saisie2 } Memo2:= CreateWindowEx(WS_EX_CLIENTEDGE, 'Edit','', WS_CHILD
or WS_VISIBLE or WS_BORDER or
ES_LEFT,5,35,140,25, hWindow, 0, Inst, nil); { Creation du
bouton} Button1 := CreateWindow('Button', 'Generer serial', WS_VISIBLE
or WS_CHILD or BS_TEXT, 6, 65,
138, 24, hwindow, 0, Inst, nil);
UpdateWindow(hWindow);
while GetMessage(TheMessage,0,0,0) do begin if not IsDialogMessage(hWindow,TheMessage) then begin TranslateMessage(TheMessage); DispatchMessage(TheMessage); end; end; end.
|
|