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


         Plan du tutoriel

Introduction
notions de base
Creation d'une dll
dll vide
dll de ressource
Utilisation de cette dll de ressources
dll de fonctions
Utilisation dynamique de cette dll de fonction
Utilisation statique de cette dll de fonction
Exemple final de programme avec utilisation de dll
 
 



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
 


    MonImage BITMAP image.bmp

     


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.