Utilisation des dll -delphi raw programming-
Par Clandestino / Fighting For Fun

type de sujet : coding

Niveau requis : 

X

X

X

X

X

  Beginner


         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
 
Annexes :
Comment compiler 'A la main'
Minidelphi : IDE de voyage...
 



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.
J'empreinte le terme 'Delphi Raw Programming' a des auteurs anglo-saxons pour designer des techniques de programmation permettant (entre autres choses) de creer des executables de petite taille.

Delphi Raw Programming (DRP):
- Le langage de developpement utilise est le pascal et surtout l'API window.
- Le compilateur utilise est dcc32.exe de borland inclus dans Borland Delphi ou Borland C++.
- On evite les vcls et composants delphis tout prets pour construire les interface 'a la main'. Les projets peuvent etre developpes avec un simple editeur de texte (le bloc note suffit) et compilee par ligne de commande...

C'est dans ce cadre, loin de tout assistant ou interface prefabriquee, que je vous propose de plonger, avec l'espoir que le savoir que je partage ici sera utile a beaucoup.
 


Creation d'une dll

Avec Delphi, le code source d'une dll est similaire à celui d'un programme, c'est un fichier texte avec l'extension *.dpr. 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 est aussi celui de ce fichier : nom_de_la_dll.dpr }

    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 intégrer des resources précompilées (*.res) à 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 pour etre compatible avec C++ ou l'asm...
    Explication : par defaut delphi utilise la convention register qui passe les parametres dans le sens inverse des autres conventions...
    Pour créer des dll compatibles avec d'autres langages, n'oubliez pas de spécifier 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) à votre fonction }
    end;
    { Définissez vos fonctions de manière classique sans oublier de spécifier la convention d'appel comme pour les procédures }

    Exports
    Nom_de_la_procedure,
    Nom_de_la_fonction;

    { Précisez après le mot clef exports le nom des fonctions et des procedures exportées par votre dll. }

    begin
    { corps de la dll : le code placé ici sera utilisé lors de l'initialisation de cette dernière... }
    end.


Attention :
- 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 dernière par votre dll si celle-ci l'a allouée

 



Exemple de dll vide
 

Creez un fichier texte et renommez-le madllvide.dpr
Copiez-y le code source suivant :


    Library
    madllvide ;
    Uses windows ;
    begin
       Messagebox(0,
    'Message','dll chargée en mémoire',MB_OK) ;
       
    { Ce message s'affichera lors du chargement de la dll }
    end.


Enregistrez les modifications puis compilez cette source (voir annexe compilation)

 


 

Exemple de Dll de ressources :

Nous allons creer une dll contenant une image.
Creez un dossier vide et creez-y un fichier texte avec 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...

Code source de Picture.rc
 


    MonImage BITMAP image.bmp

     


Compilation 'à la main' d'un fichier resource:

Copiez dans votre dossier le fichier brcc32.exe contenu dans le repertoire 'bin' de votre delphi puis faites glisser Picture.rc à la souris sur brcc32.exe pour compiler votre fichier ressource et obtenir Picture.res, ou bien utilisez brcc32 en ligne de commande...
 

Creation de la dll de ressources qui va contenir cette image
Le code est simplissime, il suffit d'indiquer au compilateur qu'il faut inclure le fichier Picture.res

Code source de dllresources.dpr
 


    library dllresources ;
    {$R Picture.res}

    begin

    end.

 


 

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 delphi et l'API windows...
Un cours sur ce sujet (niveau newbies) sera publié...( En attendant, un exemple de fenetre vide est inclus dans minidelphi...)
Creez un fichier texte nomme fenbitmap.dpr, 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.dpr
 


    Program
    fenbitmap;
    Uses Windows, Messages;         
    { les unites utilisees pour la compilation }
    Var                                             
    { les variables }
       WinClass: TWndClassA;
        Inst: HINST;
        hWindow: 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(
    'DllResources.dll') ;
            
    { en cas d'echec de chargement }
            if HandleDll = 0 then
               begin
                  messagebox(0,
    'Attention','Dll non trouvée', 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 }

                   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)}
                                round(getsystemmetrics(SM_CXSCREEN)/2)-139,        
    { distance/gauche de votre ecran ici on centre la fiche en prenant le milieu de l'ecran moins la demi largeur }
                                round(getsystemmetrics(SM_CXSCREEN)/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 delphi, ou en utilisant le compilateur dcc32 (contenu dans le repertoire bin de votre delphi) à la souris en glissant votre fichier source sur dcc32.exe ou bien avec minidelphi.

 


 

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 }
    { 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 := pchar(Texte) ;
    { 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.

 


 

Utilisation dynamique de cette Dll de fonction :

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.dpr
 


Program
MonGenerateur;
Uses Windows, Messages;
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 }
          @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,
              round(getsystemmetrics(SM_CXSCREEN)/2)-130,
              round(getsystemmetrics(SM_CXSCREEN)/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.

 


 

Comment compiler un code source DRP :

Si vous avez Delphi :
Les codes sources donnes ici peuvent directement etre compiles dans l'interface de Delphi si vous le desirez.
Vous pouvez aussi compiler un code source en faisant un drag and drop a la souris de votre fichier sur le compilateur dcc32.exe que vous trouverez dans le repertoire bin de borland Delphi ou de Borland C++
Vous pouvez aussi lancer dcc32 en ligne de commande dans une console pour compiler votre projet, vous aurez ainsi les message de reussite ou d'erreur du compilateur...

La syntaxe a utiliser :
dcc32 [options] fichier [options]

  -A<unité>=<alias> = Alias d'unité
  -B = Construire toutes les unités
  -CC = Cible console
  -CG = Cible GUI
  -D<syms> = Définir conditionnels
  -E<chemin> = Répertoire dest. EXE
  -F<offset> = Chercher erreur
  -GD = Fichier map détaillés
  -GP = Fichier map avec publics
  -GS = Fichier map avec segments
  -H = Afficher les conseils
  -I<paths> = Répertoires d'inclusion
  -J = Générer fichier .obj
  -JP = Générer fichier .obj C++
  -K<addr> = Adresse de base image

-LU<paquet> = Utiliser paquet
-M = Construire unités modifiées
-N<chemin> = destination DCU
-O<chemins> = Répertoires objets
-P = Chercher aussi fichiers 8.3
-Q = Compilation silencieuse
-R<chemins> = Répertoires ressources
-U<chemins> = Répertoires unités
-V = Infos débogage dans EXE
-VR = Générer débogage distant (RSM)
-W = Afficher les avertissements
-Z = Générer DCPs
-$<dir> = Directive de compilation
--help = Affiche cet écran d'aide
--version = Affiche le nom et la version

Options du compilateur :
-$<lettre><état> (voir défauts ci-dessous)

  A8  Champs enregistrements alignés
  B-  Evaluation booléenne complète
  C+  Evaluer assertions à l'exécution
  D+  Informations de débogage
  G+  Utiliser réf. données importées
  H+  Utiliser chaînes longues par défaut
  I+  Vérification E/S
  J-  Consts structurées en écriture
  L+  Symboles débogage locaux
  M-  Info type à l'exécution
  O+  Optimisations

P+  Params chaîne ouverte
Q-  Vérification débordement entier
R-  Vérification étendue
T-  Opérateur @ typé
U-  Division Pentium(tm) sûre
V+  Chaîne variables strictes
W-  Générer cadre de pile
X+  Syntaxe étendue
Y+  Info référence symbole
Z1  Taille mini types énumérés

 


 

Compilation avec minidelphi :

Pour compiler un projet avec minidelphi il suffit de choisir dans le menu outils le sous menu compiler :