DLL, Dynamic Library Link ou en français lien dynamique vers une librairie. Le fichier DLL est cette librairie. Le but étant au départ de permettre aux développeurs de bénéficier de fonction déjà existante et aussi de décharger la mémoire. Les DLL contiennent en effet des ressources qu'elle peuvent partager avec plusieurs programmes. C'est ressources permettent d'avoir accès à des fonctions, des composants...
Qu'est ce qu'une DLL me direz-vous ? Une dll est un fichier contenant des ressources et la capacité de mettre à disposition ces ressources pour les programmeurs et donc pour les applications. Une DLL peut contenir des fonctions (exemple des fonctions mathématiques), une classe, des composants ou d'autre chose comme des icônes.
Pour ce qui est de son utilité, on peut en distinguer trois.
Dans cette partie, nous allons voir comment écrire et utiliser une DLL de fonction, de Classe et pour terminer de composant.
Pour ceux qui préfèrent la théorie avant la pratique, regardez les chapitres suivant.
Pour faire simple, utilisons l'expert DLL, Fichier -> nouveau -> Expert DLL.
Sinon, il vous faudra supprimer dans l'unité projet.prj le mot program pour le remplacer par Library et supprimer Forms dans Uses.
Supprimez aussi tout ce qui se trouve entre begin et end et supprimer la forme du projet, pour cet exemple, elle ne servira pas.
Nous allons commencer une DLL de fonction Mathématique, libre à vous de rajouter d'autre fonction par la suite. Enregistrer le projet sous le nom LibMaths.
Notre première fonction sera la fonction factorielle. Bref rappel de Math, la factorielle de 1 vaut 0 et la factorielle de 0 vaut 1. On note l'opération factorielle : n! où n est un nombre entier positif et ! l'opérateur. Le produit factoriel de n vaut n multiplier par le produit factoriel de n-1 (pour les esprits vifs que vous êtes, cette définition vous fait penser à la récursivité). Exemple : 3! = 3*2! = 3*2*1! soit 3*2*1 donc 6. et 4! = 4*3! = 4*6 soit 24. Pour ce qui ne connaissent pas la récursivité, pas d'inquiétude car ce n'est pas le sujet du cours, l'essentiel est de comprendre le principe.
library LibMaths;
uses
Windows;
begin
end.
function Factorielle(n : integer): integer;
begin
// On n'aurait pu traité le cas 0 et 1 séparement mais cela fait un test de plus
if n = 0 then Result := 1
else Result := n * Factorielle(n-1);
end;
Vérifiez bien que la fonction puisse s'arrêter quand vous faites de la récursivité sinon vous créez une boucle infinie. |
exports
Factorielle;
Le code source complet doit ressembler à ceci. Mais attention, il ne s'agit pas d'un exécutable, vous ne pouvez que le compiler. Pour ce faire, Tapez CTRL+F9 ou Projet -> Compiler LibMaths.
library LibMaths;
uses
Windows;
function Factorielle(n : integer): integer;
begin
if n = 0 then Result := 1
else Result := n * Factorielle(n-1);
end;
exports
Factorielle;
begin
end.
Nous allons maintenant nous attaquer à la programmation d'une application utilisant cette dll. Pour cela enregistrer votre projet si ce n'est déjà fait et commencez un nouveau projet.
Dans la Form ajouter un EditBox pour la saisie de l'utilisateur et un Label ou EditBox pour afficher le résultat plus d'autres labels pour documenter votre application. Ajoutez aussi deux boutons l'un pour calculer et l'autre pour fermer. Pour le bouton Fermer vous pouvez utiliser un BitBt (onglet : Supplément) et positionner sa propriété Kind à bkClose.
implementation
function Factorielle(n : integer): integer; external 'LibMaths';
{$R *.DFM}
procedure TForm1.btTestClick(Sender: TObject);
var
n : integer;
begin
n := StrToInt(Edit1.Text);
lbResultat.Caption := IntToStr(Factorielle(n));
end;
Le code source complet ci dessous.
unit PrincipalFrm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Buttons, Mask;
type
TForm1 = class(TForm)
BitBtn1: TBitBtn;
Label1: TLabel;
btTest: TButton;
lbResultat: TLabel;
Edit1: TEdit;
procedure btTestClick(Sender: TObject);
private
{ Déclarations privées }
public
{ Déclarations publiques }
end;
var
Form1: TForm1;
implementation
function Factorielle(n : integer): integer; external 'LibMaths';
{$R *.DFM}
procedure TForm1.btTestClick(Sender: TObject);
var
n : integer;
begin
n := StrToInt(Edit1.Text);
lbResultat.Caption := IntToStr(Factorielle(n));
end;
end.
Si vous avez regardé la partie au-dessus, le premier mot nouveau fut library. Ce mot indique au compilateur qu'il ne s'agit pas d'un programme principal (contenant main) et donc sans point d'entrée de type winmain. Ensuite la partie entre begin et end. correspond comme pour les projets à la partie initialisation. Dans la partie initialisation, vous pouvez initialiser les variables globales. Voilà pour les détails.
exports : permet de lister les fonctions à rendre visible/accessible depuis l'extérieur.
exports
Toto name 'titi';
exports
Toto index 5;
function Factorielle(n : integer): integer; stdcall; export;
begin
if n = 0 then Result := 1
else Result := n * Factorielle(n-1);
end;
implementation
function Factorielle(n : integer): integer; stdcall; external 'LibMaths';
{$R *.DFM}
Manipulation des chaînes Longues :
Si vous avez utilisé l'Expert Dll, vous avez pu remarquer un commentaire vous mettant en garde sur l'utilisation des chaînes longues. Cette mise garde ne concerne que la manipulation des chaînes comme paramètre, c'est à dire entrant ou sortant de la dll (en interne, pas de problème). En effet, Delphi gère les chaînes longues d'une façon qui est incompatible avec les autres langages. Vous avez lors deux solutions soit utiliser ShareMem et inclure la dll : BORLNDMM.DLL avec votre application, soit utiliser des PChar ou ShortString (voir le chapitre 102.2.5. DLL & Chaîne de caractère). Attention même les chaînes qui sont dans des enregistrements ou des classes sont concernées ! |
Les dll permettent aussi d'exporter des classes à condition de comprendre quelque subtilité liée au classe. Commençons par définir une classe dans un projet dll (Nouveau-> expert dll). ajouter une nouvelle unité (Nouveau->Unité) qui contiendra notre classe et commencons sa définitions.
unit MaClassDLLUnt;
interface
type
TMaClass = class
private
bidon : integer;
public
function GetBidon() : integer; virtual; stdcall; export;
procedure SetBidon(NewValeur : integer); virtual; stdcall; export;
end;
implementation
function TMaClass.GetBidon():integer; stdcall; export;
begin
// Renvoie la valeur de bidon
Result := Bidon;
end;
procedure TMaClass.SetBidon(NewValeur : integer); stdcall; export;
begin
// Change la valeur de bidon en NewValeur
Bidon := NewValeur;
end;
end.
function CreeInstanceMaClass() : TMaClass; export; // Pas de virtual ici !
begin
Result := TMaClass.Create;
end;
// Page principal
library Dll_ClassPrj;
uses
SysUtils,
Classes,
MaClassDLLUnt in 'MaClassDLLUnt.pas';
{$R *.res}
exports
CreeInstanceMaClass; // Seul la fonction permettant d'instancier(créer) un objet de la classe est exporté !
begin
end.
// Unité contenant la définition de la classe
unit MaClassDLLUnt;
interface
type
TMaClass = class
private
bidon : integer;
public
function GetBidon() : integer; virtual; stdcall; export;
procedure SetBidon(NewValeur : integer); virtual; stdcall; export;
end;
function CreeInstanceMaClass() : TMaClass; stdcall; export; // Pas de virtual ici !
implementation
function TMaClass.GetBidon():integer; stdcall; export;
begin
// Renvoie la valeur de bidon
Result := Bidon;
end;
procedure TMaClass.SetBidon(NewValeur : integer); stdcall; export;
begin
// Change la valeur de bidon en NewValeur
Bidon := NewValeur;
end;
function CreeInstanceMaClass() : TMaClass; stdcall; export; // Pas de virtual ici !
begin
Result := TMaClass.Create;
end;
end.
Voilà pour la dll, nous allons créer une application l'utilisant. Contrairement à ce qui avait été dit auparavant export ne sera pas la dernière déclaration dans l'importation des fonctions. Il s'agit d'abstract qui terminera nos déclarations. Pourquoi et bien parce que mais plus sérieusement, pour pouvoir utiliser une classe il faut que l'application connaisse sa définition. C'est pourquoi nous avons déclaré toutes nos méthodes (les fonctions quand elles sont dans une classe) virtuel et que nous utilisons abstract ce qui permet de mettre la définition sans implémenter les méthodes (voir les classes pour plus de précision).
Dans un nouveau projet, voici pour l'apparence :
unit UtilDll_ClassUnt;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TForm1 = class(TForm)
edNouvValeur: TEdit;
Label1: TLabel;
lbValeur: TLabel;
btManipClasse: TButton;
Label2: TLabel;
procedure btManipClasseClick(Sender: TObject);
private
{ Déclarations privées }
public
{ Déclarations publiques }
end;
type
TMaClass = class
private
// On n'importe pas Bidon car l'utilisateur ne devrait jamais
// Manipuler directement cet valeur, voir les classes
// pour comprendre la philosophie sous jascente.
// pas de : bidon : integer;
public
function GetBidon() : integer; virtual; stdcall; export; abstract;
procedure SetBidon(NewValeur : integer); virtual; stdcall; export; abstract;
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
function CreeInstanceMaClass() : TMaClass; stdcall; external 'Dll_ClassPrj'; // Pas d'abstract ici, ce n'est pas une méthode !
procedure TForm1.btManipClasseClick(Sender: TObject);
var
MaClass : TMaClass;
begin
// La classe ne fait pas grand chose
// mais permet de rester concentrer sur le sujet : les dll
// Crée une instance de la classe
MaClass := CreeInstanceMaClass();
// Affecte une nouvelle valeur à bidon
MaClass.SetBidon(StrToInt(edNouvValeur.Text));
// recupère la valeur bidon et l'affiche
lbValeur.Caption := IntToStr(MaClass.GetBidon);
// libère la classe
MaClass.Free;
end;
end.
Nous allons réaliser une petite calculette sans prétention. Le but est d'apprendre à utiliser des composants Delphi dans une DLL et de voir quelques écueils.
Commencez un nouveau projet DLL et ajoutez une fiche. Enregistrer le projet (PMadll) et l'unité (UMadll). Dans la fiche ajouter les composants et changer la propriété BorderStyle à bsDialog de la fiche, de façon à obtenir ce résultat :
procedure TMaForm.Button1Click(Sender: TObject);
begin
Label3.Caption := FloatToStr(StrToFloat(Edit1.Text) + StrToFloat(Edit2.Text));
end;
procedure Creer_Form; stdcall; export;
begin
// Crée une instance de Form
DecimalSeparator := '.'; // change le séparateur décimal
Application.CreateForm(TMaForm, MaForm);
MaForm.Label3.Caption := '0';
end;
procedure Free_Form; stdcall; export;
begin
// Libère la Form
MaForm.Free;
end;
function fncsomme():Double; stdcall; export;
begin
with MaForm do
begin
// Montre la forme et attend sa fermeture
ShowModal;
// Renvoi le résultat du Calcul
Result := StrToFloat(Label3.Caption);
end;
end;
procedure proSomme(var R: Double); stdcall; export;
begin
with MaForm do
begin
// Montre la forme et attend sa fermeture
ShowModal;
// Modifie la variable R (passage par adresse)
R := StrToFloat(Label3.Caption);
end;
end;
exports
Creer_Form, Free_Form,
fncsomme, proSomme;
Réalisons maintenant une application qui utilisera cette dll. Enregistrer votre projet dll si ce n'est déjà fait et commencez un nouveau projet application. Nous allons faire simple et nous contenter d'un bouton pour appeler la calculette et d'un label pour afficher le résultat, nous mettrons aussi la fiche en bsdialog.
L'apparence étant faite, passons au codage. Il nous faut importer les fonctions permettant de manipuler la DLL. Donc nous allons importer les fonctions qui permettent de créer la form, de la libérer et bien sûr d'utiliser la calculette.
Juste après les directives de compilation ( {$R *.DFM} ) ajoutez :
procedure Creer_Form; stdcall; external 'pMadll.dll';
procedure Free_Form; stdcall; external 'pMadll.dll';
function Total():Double; stdcall; external 'pMadll.dll' name 'fncsomme';
procedure TfmUseDLL.btAppelCalculetteClick(Sender: TObject);
var
R : Double;
begin
Creer_Form;
R := Total();
lbResultat.Caption := FloatToStr(R);
// Ne pas oublier de libérer la form à la fin
Free_Form;
end;
Si votre dll doit utiliser des chaînes longues comme paramètre, vous allez vous heurter à un problème. Heureusement deux solutions s'offrent à vous.
Je ne parlerais ici que des PChars et de leur utilisation avec les dll, le reste ne posant pas de difficulté. Tout d'abord un PChar est une chaîne un peu spéciale (rien que le nom est bizarre mais très révélateur). Les Pchars sont des pointeurs sur une chaîne de Char terminé par un indicateur de fin. Une bonne nouvelle lorsqu'on les manipule sous delphi, ils sont compatibles avec les string.
MonPChar := MonString;
Par contre, l'indicateur de fin est le caractère #0. Du coup ce caractère ne doit pas se retrouver dans la chaîne (la chaîne s'arrête dés qu'elle rencontre celui-ci). Cet indicateur de fin est aussi appelé null ou Zéro Terminal.
Le point le plus délicat est l'espace mémoire occupé par ces PChars, contrairement au string, il est fixe. Mais avant d'entrer dans le vif du sujet un exemple d'utilisation vous permettra de fixer les choses :
function MaFonction(S :PChar; Taille : integer);
On ne met pas var devant S car c'est un pointeur donc une adresse (passer une adresse d'une adresse :o )
var
U : array[0..51] of Char; // Déclaration d'une chaîne de caractère
begin
U := 'affectation d''une valeur';
MaFonction(U, sizeof(U));
... Suite des instructions...
end;
var
U : PChar; // Déclaration d'un pointeur sur chaîne de caractère
begin
U := StrAlloc(50);
StrLPCopy(U, 'affectation d''une valeur', 49);
MaFonction(U, 50); // Pas de Size de U surtout !
... Suite des instructions...
end;
Les notions vue ici sont aussi valables pour transmettre des tableaux n'étant pas des tableaux de caractère. Il suffit de remplacer les pointeurs PChar par un pointeur sur votre tableaux.
Exemple avec un tableau de Double.
var
Mat : array[0..5] of double;
begin
for i:=0 to 5 do
Mat[i] := 0;
TransmettreMat(Mat[0], 5); // On transmet aussi la Taille !
end;
procedure transTableau(var T : Double;taille : integer);stdcall;export;
type
TTabMat = array[0..6] of double;
PTabMat = ^TTabMat;
var
i : integer;
pMat : PTabMat;
begin
// fixe le séparateur décimal
DecimalSeparator := '.';
// fait pointer pMat sur l'adresse de T donc sur le tableau transmit
pMat := @T;
for i:=0 to taille do
pMat^[i] := pMat^[i] + 1.33;
end;
Nous avons jusqu'à présent charger les dll de façon statique. Ceci présente plusieurs avantage, vous savez tout de suite si votre application n'arrive pas à charger votre dll, le code est plus compact et vous ne vous posez pas de question sur le déchargement de la dll. Par contre la dll est toute de suite chargée en mémoire et elle ne libèrera cet espace que lorsque l'application sera terminée, si les ressources de la dll ne sont utilisées que ponctuellement quel gaspillage. Nous allons apprendre ici à charger les dll de façon dynamique, c'est à dire les chargé en mémoire que pour le temps de leur utilisation effective. Un autre avantage est la création de plugin (mais je n'en sais pour l'instant pas plus).
Voyons tout de suite les nouveaux mots clé :
D'abord, il faut charger la dll pour cela, on utilise LoadLibrary :
Handle := loadlibrary('MonfichierDll.dll');
Le handle permet de pointer sur cette dll (pensez aux objets d'une classe), si le chargement échoue le handle à une valeur nulle.
Ensuite, il faut charger chaque fonction dont on n'aura besoin grâce à GetProcAddress :
@MaFonction := GetProcAddress(Handle, 'nomdelafonctiondanslaDLL');
Où nomdelafonctiondanslaDLL est le nom de la fonction que l'on veut importer et MaFonction un pointeur sur processus, ici on récupère son adresse !
Si le chargement de la fonction échoue le pointeur renvoie nil.
Lorsque la dll n'est plus requise, il faut la décharger par l'appel à FreeLibrary
FreeLibrary(Handle);
unit PrincipalFrm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Buttons, Mask;
type
TForm1 = class(TForm)
BitBtn1: TBitBtn;
Label1: TLabel;
btTest: TButton;
lbResultat: TLabel;
Edit1: TEdit;
procedure btTestClick(Sender: TObject);
private
{ Déclarations privées }
public
{ Déclarations publiques }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.btTestClick(Sender: TObject);
procedure TForm1.btTestClick(Sender: TObject);
type
// Déclare un type function pour pouvoir manipuler Factorielle
TMyProc = function(x : integer):integer;
var
n : integer;
Handle: THandle;
MaFactorielle: TMyProc;
begin
n := StrToInt(Edit1.Text);
// Charge une dll dynamiquement et la libère ensuite
Handle := loadlibrary('LibMaths.dll'); // Charge la dll
if Handle <> 0 then
begin
try
// Charge dynamiquement une fonction de la dll
@MaFactorielle := GetProcAddress(Handle, 'Factorielle');
if @MaFactorielle <> nil then
begin
lbResultat.Caption := IntToStr(MaFactorielle(n));
end;
Finally
FreeLibrary(Handle); //Assure le déchargement de la dll
end; // Try..Finally
end
else
ShowMessage('Impossible de charger la DLL');
end;
end.
La déclaration de fonction a disparu et dans l'événement onclic du bouton, on charge la dll, la fonction Factorielle et une fois le travail accomplit, on libère la DLL.
Remarque importante : pour le type TMyProc, il faut reprendre la convention d'exportation déclaré dans la dll.
Libération des ressources :
Remarquez l'imbrication du try..finally : On essaie d'abord de charger la dll si l'opération réussit (ici traité par un if mais un try..except est tout à fait valable), on rentre dans un bloc protégé (try) même si une exception arrive la ressource sera libérée car elle est dans la partie finally. Par contre, ce serait une erreur de mettre loadlibrary dans le code protégé. |
Dans l'intro, vous avez pu lire que la DLL pouvait être écrite dans un langage et utiliser dans un autre. Je ne vous présenterais pas ici des dll écrite dans un autre langage et utilisé dans Delphi car cela à peut d'intérêt. Il vous suffit de reprendre ce que l'on a déjà vu. Parlons plutôt du cas où la Dll a été écrite en Delphi et l'application dans un autre langage. Une chose importante, les conventions d'appel dans la DLL doivent être compatibles avec le langage utilisé pour l'application. En général stdcall.
Un autre point très délicat concerne les paramètres manipuler par la dll, notamment les chaînes longues voir 102.2.5. DLL & Chaîne de caractère. Mais aussi le composant Menu qui pour une raison qui m'échappe ne marche qu'avec des applications Delphi.
Les précautions d'usage étant établies, essayons de réaliser deux exemples. Le premier utilisera le langage VB et le second HT Basic avec un but identique manipuler la Calculette que nous avons créée dans le chapitre 102.2.4. DLL de composant. Que vous n'ayez ni l'un ni l'autre de ces langages n'a pas vraiment d'importance, c'est exemple n'étant que didactique. L'important est de savoir comment importer les ressources contenues d'une dll dans le langage que vous utilisez.
Reprenons notre dll Calculette et copions la dans le répertoire où se retrouvera l'application en VB.
Crée un nouveau projet VB et occupons-nous de son apparence en réalisant une fiche dans ce style :
Private Declare Sub CreerFormDll Lib "PMadll.dll" Alias "Creer_Form" ()
Private Declare Sub FreeFormDLL Lib "PMadll.dll" Alias "Free_Form" ()
Private Declare Function DllTotal Lib "PMadll.dll" Alias "fncsomme" () As Double
Private Sub btAppelCalculette_Click()
CreerFormDll
lbResultat.Caption = DllTotal
' N'oublions pas de libérer la Form
FreeFormDLL
End Sub
Ci dessous le code source complet d'une application écrite en HT Basic utilisant la calculette écrite en Delphi. Comme je l'avais déjà dit auparavant, peu importe que vous utilisiez ce langage ou non. Notez par contre la similitude du codage dans les différents langages.
DLL UNLOAD ALL
! Change le répertoire en cours
MASS STORAGE IS "D:\delphi\Utilisation_de_la_DLL_par_HTB\dll"
! Charge la DLL
DLL LOAD "pMaDll"
! Importe les fonctions et les renomme.
DLL GET "STDCALL VOID pMaDll:Creer_Form" AS "Creerformdll"
DLL GET "STDCALL VOID pMaDll:Free_Form" AS "Freeformdll"
DLL GET "STDCALL VOID pMaDll:proSomme" AS "Dlltotal"
! rechange de répertoire
MASS STORAGE IS "D:\delphi\Utilisation_de_la_DLL_par_HTB"
! Un peu de ménage
CLEAR SCREEN
! Affiche à l'écran toutes les fonctions disponibles dans la dll (pas seulement celle qui sont importées)
LIST DLL
REAL R
! Crée la Form
Creerformdll
! Affiche la Form en Modal (Mécanisme interne de la fonction DllTotal)
Dlltotal(R)
! Libère la form
Freeformdll
! Affiche le résultat à l'écran
PRINT R
! Décharge la DLL et oui en HTB, il faut tout faire soi même ;)
DLL UNLOAD ALL
! Fin
END
Cette partie est ajout au texte original :
Suite aux emails que j'ai reçus, j'ai décidé d'apporter quelques complèments d'informations, fournissant ainsi à la masse les informations qu'une minorité à reçu. Je tiens à préciser qu'il ne faut hésiter à poser vos questions à la mailling liste car elle est faite pour cela :) |
Inclure la form de la DLL dans l'application l'utilisant. (Ne pas afficher la form de la dll dans la barre des taches)
Si vous avez réalisez l'exemple ou d'autre dll contenant des forms, vous avez peut être remarquer la présence dans la barre des taches d'un nouvelle onglet portant le nom de la form. Ceci n'est pas forcement ce que vous souhaitez (comme ce fut le cas de la personne qui m'a soumis ce problème), sans compter que ce n'est pas du plus bel effet. Rasurer vous il existe un moyen et en plus, c'est un moyen simple pour ne pas avoir la forms dans la barre des taches. Il suffit de donner à la dll le handle de votre application (il s'agit d'un identifiant pour ceux que ça intéresse).
Dans la dll, au moment de crée la form principal, il faut changer le handle de l'application pour qu'il pointe sur celui de l'application appelante. Par exemple si on reprend la dll du chapitre : 102.2.4. DLL de composant, il suffit de changer la dll ainsi :
procedure Creer_Form(HandlePere: HWND);stdcall;export;
begin
// Changer du séparteur décimal
DecimalSeparator := '.';
// Lie la dll à l'application appelante
Application.Handle := HandlePere;
// Crée la form (de la dll)
Application.CreateForm(TMaForm, MaForm);
// init
MaForm.Label3.Caption := '0';
end;
Ajouter l'unité Windows dans la clause uses pour avoir les handles, si vous l'oubliez la dll ne se compilera plus.
Ensuite dans le projet utilisant cette dll, on transmet le handle de l'application à la dll. Toujours dans le cadre de cette dll,
Pour la déclaration dans l'unité du projet :
procedure Creer_Form(HandlePere : HWND);stdcall;external 'pMadll.dll';
Et lorsque l'on crée la form de la dll :Creer_Form(Application.Handle);
Veuillez notez que ce code fonctionne mais je trouve qu'il me manque des informations au niveau de la libération de la form lorsqu'elle est gérée ainsi. Donc si vous rencontrez des problèmes ne me jeter pas la pierre, je n'y suis pour rien et si vous avez plus d'info, je suis preneur comme toute la communauté.
Petite astuce de débogage :
Pour explorer le code de votre dll pendant l'exécution, ouvrez votre projet dll dans exécuter choisissez paramètre et indique une application exploitant votre dll dans Application hôte. la dll se comporte alors comme si elle était exécutable, vous pouvez placer des points d'arrêt, faire du pas à pas ... |
Merci à Lionel Saliou pour son aide dans la correction.