FAQ DelphiConsultez toutes les FAQ
Nombre d'auteurs : 124, nombre de questions : 933, dernière mise à jour : 28 septembre 2024 Ajouter une question
Cette FAQ a été réalisée à partir des questions fréquemment posées sur les forums Delphi et Delphi et bases de données de www.developpez.com et de l'expérience personnelle des auteurs.
Nous tenons à souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle propose soient correctes. Les auteurs font le maximum, mais l'erreur est humaine. Cette FAQ ne prétend pas non plus être complète. Si vous souhaitez y apporter des corrections ou la compléter, contactez un responsable (lien au bas de cette page).
Nous espérons que cette FAQ saura répondre à vos attentes. Nous vous en souhaitons une bonne lecture.
L'équipe Delphi de Developpez.com.
- Qu'est-ce qu'une DLL ?
- Comment connaître les fonctions contenues dans une DLL ?
- Comment tester l'existence d'une fonction dans une DLL ?
- Comment appeler une fonction dans une DLL ?
- Comment connaître la liste des DLL utilisées par mon programme ?
- Comment obtenir les informations de version d'un programme ?
- Pourquoi j'obtiens le message "Impossible d'affecter TFont à TFont" ?
- Comment partager un objet entre un exécutable (.exe) et une DLL?
- Comment passer des strings dans une DLL ?
- Comment passer une chaîne de caractères d'une DLL à une application ?
- Comment passer un tableau d'entiers à une DLL ?
"DLL" signifie "Dynamic-Link Library" ou, en Français, "Bibliothèque à liaison dynamique".
Il s'agit d'un fichier contenant, à l'instar d'un exécutable, du code compilé compréhensible par le processeur. La différence avec un exécutable réside dans le mode d'exploitation de ce code :
il est présent dans la DLL sous forme de différentes fonctions/procédures exportées, c'est-à-dire disponibles "de l'extérieur".
Ainsi tout exécutable, s'il a accès à la DLL, peut appeler et recevoir le résultat d'une fonction qu'elle contient. La seule contrainte est de connaitre la liste et les types des paramètres de la fonction appelée. Ceci explique le terme "bibliothèque" : un ensemble de fonctions exportées.
La partie "liaison dynamique" explicite le fait que le chargement d'une fonction d'une DLL se fait lors de l'exécution de l'application appelante, donc dynamiquement. On commence par charger en mémoire le code de la fonction à appeler (c'est le système qui s'en charge), puis on passe les paramètres pour passer à l'exécution du code de la fonction.
Un exemple de DLL assez courant nous est fourni par Microsoft : les contrôles ActiveX. Il s'agit de DLL qui exportent ici une interface et ses fonctions, permettant la création et l'exploitation du contôle développé et déployé par l'intermédiaire de cette DLL.
L'avantage de bibliothèques comme les DLL est de donner la possibilité d'externaliser des fonctions, d'en regrouper par domaines d'application et surtout de rendre ces fonctions réutilisables à partir de plusieurs applications.
Il faut utiliser l'utilitaire TDUMP, fourni avec Delphi. Dans une fenêtre de commandes :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 | C:\WINNT\SYSTEM32>tdump -ee -m zipdll.dll Turbo Dump Version 5.0.16.12 Copyright (c) 1988, 2000 Inprise Corporation Display of File ZIPDLL.DLL EXPORT ord:0001='GetZipDllVersion' EXPORT ord:0002='ZipDllExec' EXPORT ord:0003='___CPPdebugHook' |
On peut également utiliser l'outil gratuit Dependency Walker, qui montre en plus les dépendances entre modules.
Voici une fonction pour tester si une fonction existe dans une DLL. Ceci permet d'éviter les erreurs de liaison dynamique, suivant les versions de Windows par exemple.
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 | function FunctionDetect(LibName, FuncName: string):boolean; Var LibHandle:THandle; begin LibHandle := LoadLibrary(PChar(LibName)); Result := (LibHandle<>0)And(GetProcAddress(LibHandle, PChar(FuncName)) <> nil); end; |
Les fonctions ou procédures exportées d'une DLL sont accessibles à tout exécutable qui en connaît les paramètres. Il existe deux méthodes de liaison avec une DLL : la méthode statique, et la méthode dynamique.
Pour illustrer ces deux méthodes, imaginons que vous disposiez d'une DLL 'exemple.dll' qui exporte une fonction sous le nom de 'Somme' avec un index égal à 1. Cette fonction prend pour paramètre deux Integer, et renvoie également un Integer.
Méthode statique
Il s'agit de la méthode la plus simple à mettre en place. Il faut commencer par déclarer la fonction à importer dans votre unité de la manière suivante :
Code delphi : | Sélectionner tout |
function Somme(A, B: Integer): Integer; external 'exemple.dll' name 'Somme';
Préciser le nom était ici facultatif, dans la mesure où la déclaration de la fonction utilisait déjà le nom d'exportation voulu dans la DLL.
Il est également possible de demander l'importation par index :
Code delphi : | Sélectionner tout |
function Somme(A, B: Integer): Integer; external 'exemple.dll' index 1;
Code delphi : | Sélectionner tout |
ShowMessage(IntToStr(Somme(1, 2)));
Méthode dynamique
Contrairement à la méthode statique où Delphi se charge de la liaison avec la DLL en fonction de vos déclarations, la méthode dynamique vous donne toute latitude sur ce plan.
Il faut donc charger en mémoire la DLL, y récupérer l'adresse de la procédure/fonction qui vous intéresse puis utiliser cette adresse pour l'appeler.
Cela se fait avec quelques API. Voici comment charger puis appeler la fonction 'Somme' de la DLL 'exemple.dll' :
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | procedure ChargerEtAppelerSomme; var HandleDLL: THandle; Somme: function (A, B: Integer): Integer; begin Somme := nil; //Chargement de la DLL HandleDLL := LoadLibrary(pAnsiChar('exemple.dll')); //Si la DLL n'est pas chargée on ne continue pas If HandleDLL = 0 then Exit; //Récupération de l'adresse de la fonction nommée 'Somme' Somme := GetProcAddress(HandleDLL, pAnsiChar('Somme')); //Appel de la fonction si on a bien récupéré son adresse If Assigned(Somme) then ShowMessage(IntToStr(Somme(1, 2))); end; |
L'adresse de Somme est ici récupérée grâce à son nom. Elle pourrait l'être grâce à son index en utilisant cette ligne de code :
Code delphi : | Sélectionner tout |
Somme := GetProcAddress(HandleDLL, pAnsiChar('1'));
Il peut être utile de connaître la liste des Dlls utilisées par l'application lors de la diffusion de celle-ci. La procédure donnée ci-dessous retourne la liste des Dlls actuellement chargées par le programme.
Cette fonction n'est utilisable que sous Windows NT ou supérieur.
Code Delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | function GetLoadedDLLList(liste: TStrings;Options: TModuleOptions): Boolean; type TModuleArray = array[0..1] of HMODULE; PHModule = ^TModuleArray; EnumModType = function (hProcess: Longint; lphModule: PHModule; cb: DWord; var lpcbNeeded: DWord): Boolean; stdcall; var psapilib : HModule; EnumProc : EnumModType; Modules : PHModule; Nombre : DWord; i : Longint; FileName : array[0..MAX_PATH] of Char; S : string; begin Result := False; Liste.Clear; { Cette fonction n'est possible que sous Windows NT ou supérieur} if Win32Platform <> VER_PLATFORM_WIN32_NT then Exit; { Ouverture de la DLL pour obtenir la fonction voulue} psapilib := LoadLibrary('psapi.dll'); if psapilib = 0 then Exit; try { Recherche de l'adresse de la fonction d'énumération} EnumProc := GetProcAddress(psapilib, 'EnumProcessModules'); if not Assigned(EnumProc) then Exit; { Le premier appel permet de connaître le nombre d'éléments} if Not EnumProc(GetCurrentProcess, Nil , 0 , Nombre) then Exit; { Réservation de la mémoire ne fonction du nombre} GetMem(Modules,Nombre*SizeOf(HMOdule)); Try { Obtention de la liste des modules} if EnumProc(GetCurrentProcess, Modules, Nombre*SizeOf(HMOdule), Nombre) then begin for I := 0 to Nombre-1 do begin { Pour chaque module, obtention du nom complet} GetModuleFileName(Modules^[i], FileName, MAX_PATH); { Et ajout dans la liste ( seulement les Dll )} if CompareText(ExtractFileExt(FileName), '.dll') = 0 then begin S := FileName; if moRemovePath in Options then S := ExtractFileName(S); { Stockage de la chaîne} if liste.IndexOf(S) = -1 Then liste.Add(S); end; end; end; Finally FreeMem(Modules) End; Result := True; finally FreeLibrary(psapilib); end; end; |
Pour utiliser cette fonction, il suffit de passer un TStrings en paramètre ; le deuxième paramètre permet de sélectionner entre le nom complet des dll (avec chemin) ou le nom seul des Dlls.
Voici un exemple d'appel :
Code Delphi : | Sélectionner tout |
1 2 3 4 | procedure TForm1.Button1Click(Sender: TObject); begin GetLoadedDLLList(Memo1.Lines,[]); end; |
La procédure suivante permet d'obtenir les informations de version d'un programme win32.(EXE, DLL, DRV, etc…). La procédure donnée ci-dessous retourne la liste des informations de version :
Code Delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | procedure GetFileInfo2(const FileName: string;liste: TStrings); const VersionInfo: array [1..8] of string = ('FileDescription', 'CompanyName' , 'FileVersion', 'InternalName' , 'LegalCopyRight', 'OriginalFileName', 'ProductName' , 'ProductVersion'); var Handle : DWord; Info : Pointer; InfoData : Pointer; InfoSize : LongInt; DataLen : UInt; LangPtr : Pointer; InfoType : string; i : integer; begin { Demande de la taille nécessaire pour stocker les infos de Version} InfoSize:=GetFileVersionInfoSize(PChar(FileName), Handle); if (InfoSize > 0) then begin { Réservation de la mémoire nécessaire} GetMem(Info, InfoSize); try if GetFileVersionInfo(PChar(FileName), Handle, InfoSize, Info) then begin for i:= 1 to 8 do begin InfoType := VersionInfo[i]; if VerQueryValue(Info,'\VarFileInfo\Translation',LangPtr, DataLen) then InfoType:=Format('\StringFileInfo\%0.4x%0.4x\%s'#0,[LoWord(LongInt(LangPtr^)), HiWord(LongInt(LangPtr^)), InfoType]); if VerQueryValue(Info,@InfoType[1],InfoData,Datalen) then liste.add(VersionInfo[i]+' : '+strPas(InfoData)); end; end; finally FreeMem(Info, InfoSize); end; end; end; |
Exemple d'appel de la fonction :
Code Delphi : | Sélectionner tout |
1 2 3 4 | procedure TForm1.Button2Click(Sender: TObject); begin GetFileInfo('c:\windows\system\user32.dll',Memo1.lines); end; |
Lien MSDN : VerQueryValue function
Lien MSDN : VS_FIXEDFILEINFO structure
Vous avez créé un composant dans une DLL ou un paquet, que vous avez ensuite placé sur une form de l'exe via Parent, et lors de cette affectation à Parent, vous obtenez le message d'erreur " Impossible d'affecter TFont à TFont". Alors vous avez la réponse ici.
Cause La faute est à la propriété ParentFont du composant qui est positionnée à True.
Que fait cette propriété ? Si elle est à True, le composant recopie sa fonte à partir de son parent, via la méthode Assign de TFont.
C'est lors de ce Assign que plante l'application. Cette méthode, telle qu'implémentée dans TFont, recopie, pour des questions de performances, uniquement le Handle des deux fontes.
Hors un handle Windows ne peut être partagé par plusieurs modules. Les deux modules en question ici sont la DLL/le paquet et l'exécutable.
Donc lorsque Assign vérifie que le handle qu'il affecte est correct, il génère une exception. Cette exception est celle que vous obtenez.
Solution
Simplement passez à False la propriété ParentFont avant de modifier le parent du composant.
Lorsque notre application commence à prendre de l'ampleur, ou tout simplement lorsque nous cherchons à réutiliser au mieux notre code, nous pensons vite à l'utilisation des DLL. D'abord pour y mettre de simples fonctions puis, rapidement, nous avons besoin de partager des éléments plus complexes, voire des objets et là généralement les choses se gâtent (qui n'a pas eu droit aux violations d'accès ?).
Pour faire cela nous avons deux possibilités : soit nous utilisons une méthode plus ancienne qui consiste à utiliser une classe mère, soit nous utilisons les interfaces. Mais dans tous les cas, l'utilisation de l'unité Sharememest indispensable pour pouvoir partager certains types de données comme les stringet pour pouvoir partager des objets. Cette unité doit être déclarée en tête des usesdu projet principal ainsi que du projet de la DLL.
De plus l'utilisation de sharemem implique la distribution de la DLL 'BORLNDMM.DLL' avec notre application. Une alternative à sharemem existe cependant, il s'agit de FastSharemem que nous pouvons trouver ici : http://www.codexterity.com/fastsharemem.htm.
Celle-ci est plus rapide à l'exécution et ne nécessite aucun déploiement supplémentaire. Son emploi est simple puisqu'il suffit de remplacer dans les uses sharemem par Fastsharemem.
Première méthode : utilisation d'une classe mère
Principe : il s'agit de créer une ossature de classe qui ne contiendra que des méthodes abstraites mais qui sera connue de notre application et de nos DLL.
Exemple : supposons que nous souhaitons partager un objet qui possède une méthode qui fait l'addition de deux nombres, voici une classe mère qui pourrait servir à notre application et à nos DLL :
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | unit uClasseMere; interface Type TClasseMere = class Private FOperande1 : integer; FOperande2 : integer; public function Addition : integer; virtual; abstract; Property Operande1 : Integer read FOperande1 write FOperande1; Property Operande2 : Integer read Foperande2 write Foperande2; end; implementation end. |
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | unit uObjetTest; interface uses uClasseMere; Type TObjetTest = class( TClasseMere) public function Addition :integer; end; implementation function TobjetTest.Addition:Integer; begin result := Operande1 + Operande2; end; end. |
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | library pluginDLL1; uses ShareMem, SysUtils, Classes, uObjetTest in 'uObjetTest.pas'; {$R *.res} function Execute( aObjet : TObjetTest):integer; stdcall; begin result := aObjet.Addition; end; Exports Execute; begin end. |
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | uses uClasseMere, uObjetTest; ......... // Déclaration statique de la DLL function Execute( aObjet : TclasseMere):integer; stdcall; external 'PluginDLL1.dll'; procedure TForm1.Button1Click(Sender: TObject); var wObjetTest : TObjetTest; begin wObjetTest := TObjetTest.Create; try wObjetTest.Operande1 := 3; wObjetTest.Operande2 := 2; showMessage( inttostr( Execute( wObjetTest))); finally Free; end; end; |
Deuxième méthode : Utilisation des interfaces
Principe : Une interface est un peu comme une classe à quelques différences près tout de même : elle ne possède aucune propriété mais uniquement des méthodes, elle n'implémente aucune des méthodes qu'elle définit et par conséquent, elle ne peut être instanciée. Une interface est destinée à être héritée par une classe qui devra implémenter les méthodes qu'elle a définies. En quelque sorte, une interface est un contrat passé avec la ou les classes qui en héritent.
Vous l'avez donc sûrement deviné, le principe va consister à définir une interface connue de notre application ainsi que de nos DLL :
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 | unit uInterface; interface type IAddition = Interface function Calcul:integer; end; implementation end. |
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | library PluginDLL1; uses ShareMem, SysUtils, Classes, uInterface in 'uInterface.pas'; {$R *.res} function Execute( aObjet : IAddition):integer; stdcall; begin result := aObjet.Calcul; end; exports Execute; begin end. |
Définition de l'objet :
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | unit uObjetTest; interface Uses uInterface; Type TObjetTest = class( TinterfacedObject, IAddition) Private FOperande1 : integer; FOperande2 : integer; public function Calcul :integer; Property Operande1 : Integer read FOperande1 write FOperande1; Property Operande2 : Integer read Foperande2 write Foperande2; end; implementation function TobjetTest.Calcul:Integer; begin result := FOperande1 + FOperande2; end; end. |
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | uses uInterface; ......... // Déclaration statique de la DLL function Execute( aObjet : IAddition):integer; stdcall; external 'PluginDLL1.dll'; procedure TForm1.Button1Click(Sender: TObject); var wObjetTest : TObjetTest; begin wObjetTest := TObjetTest.Create; try wObjetTest.Operande1 := 3; wObjetTest.Operande2 := 2; showMessage( inttostr( Execute( wObjetTest))); finally wObjetTest :=nil end; end; |
La particularité du type string sous Delphi pose un évident problème de compatibilité avec le standard d'échange entre un programme exécutable et une DLL. Windows étant entièrement "C++ Like", les chaines de caractères sont des pointeurs. Il existe donc différentes méthodes pour passer un type string en paramètre dans une DLL…
- Utiliser les PChar
- Utiliser ShareMem : comme vous avez pu le constater, lorsque vous utilisez l'assistant de Delphi pour générer un projet de DLL, on peut lire un laïus à propos de ShareMem. Il suffit en fait de déclarer l'unité ShareMem en premier dans la liste des uses et on peut dès lors utiliser directement les string en paramètre dans les DLL. Cependant cette méthode comporte un inconvénient: celui de la portabilité. Il est en effet impératif de fournir, avec l'exécutable, le fichier "borlndmm.dll"
(Borland Memory Manager: ce fichier se trouve dans "bin" de votre installation de Delphi). - Utiliser FastShareMem : cette unité est ce que l'on pourrait appeler une "solution miracle".
Pas de DLL externe à fournir, plus rapide que ShareMem (jusqu'à 8 fois selon le créateur), il suffit juste, comme ShareMem, de déclarer FastShareMem en premier dans les uses... Cerise sur le gateau: il est FreeWare...
Lorsque vous ne souhaitez pas utiliser d'unité voire de DLL supplémentaire avec vos DLL, vous devez proscrire le type String dans les échanges de paramètres entre vos applications et vos DLL.
Il faut donc trouver une alternative à ce type. Le type de chaîne de caractères standard de Windows se trouve être la chaîne à zéro terminal (AZT), correspondant au type pChar dans Delphi.
Ce type étant un pointeur, il faut allouer de l'espace mémoire avant de pouvoir stocker la chaîne.
On se retrouve donc avec un problème : si une DLL alloue de la mémoire pour un pChar qu'elle passera à l'application lors de l'appel d'une fonction, elle ne sera pas en mesure de libérer cette mémoire par la suite, et l'application ne pourra pas non plus effectuer cette tâche, qui pour des raisons évidentes ne lui revient pas.
Il existe alors deux solutions. La première, un peu lourde, consiste à créer une fonction dédiée à la libération de la chaîne dans la DLL.
La seconde est une astuce couramment utilisée dans les API Windows : dans un premier temps, l'application appelle la fonction qui doit lui renvoyer un pChar (le pChar étant passé par référence dans les paramètres) avec nilen paramètre, ce qui indiquera à la fonction qu'elle doit renvoyer la taille de mémoire à allouer. L'application réserve alors l'espace indiqué, puis rappelle la fonction en lui passant le pointeur vers la zone mémoire nouvellement créée pour récupérer le résultat.
Une fois ce dernier exploité, l'application sera en mesure de libérer elle-même la mémoire allouée à la chaîne.
Voici une illustration de ce procédé :
Dans la DLL :
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | Dans la DLL : //La fonction renvoie à proprement parler la taille de la chaîne, le message est passé en paramètre function MessageTexte(Buffer: pChar): Cardinal; stdcall; var msg: String; begin //On récupère le résultat. Notez que le type string n'est pas proscrit en interne msg := FonctionQuiRenvoieLeMessage(); Result := Length(msg); //Si le buffer a été alloué, on y effectue une copie de la chaîne if Buffer <> nil then StrCopy(Buffer, pAnsiChar(msg)); end; exports MessageTexte; |
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | function MessageTexte(Buffer: pChar): Cardinal; stdcall; external 'MaDLL.dll'; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); var Taille: Cardinal; Buf: pChar; begin //On récupère la taille du message Taille := MessageTexte(nil); //Puis on alloue la mémoire nécessaire GetMem(Buf, Taille); //On récupère/traite le résultat MessageTexte(Buf); ShowMessage(String(Buf)); //Et enfin on peut libérer la mémoire FreeMem(Buf); end; |
De plus, un unique appel est fait à la DLL pour récupérer la taille et le message.
Voici un exemple de code :
Dans la DLL :
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | uses ..., Windows; ... function MessageTexte(var Atome: Word): Cardinal; stdcall; var msg: pAnsiChar; begin msg := 'message'; Atome := GlobalAddAtom(msg); Result := Length(msg); end; exports MessageTexte; |
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | function MessageTexte(var Atome: Word): Cardinal; stdcall; external 'dll.dll'; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); var Atome: Word; Taille: Cardinal; Buf: pChar; begin Taille := MessageTexte(Atome); GetMem(Buf, Taille); GlobalGetAtomName(Atome, Buf, Taille); ShowMessage(Buf); FreeMem(Buf); GlobalDeleteAtom(Atome); end; |
Lors de l'utilisation d'une DLL, nous pouvons être confrontés à quelques difficultés lors du passage d'un tableau en paramètre.
Voici comment procéder…
Soit une fonction d'une DLL écrite en C++ :
Code c++ : | Sélectionner tout |
int WINAPI RemplirTableau(int* TabInt);
Code delphi : | Sélectionner tout |
FDLLRemplirTableau: function(var iTab : integer): Integer;stdcall;
Et nous l'utilisons de la manière suivante :
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | var ... MonTab : array of integer; MaValeurRetour : integer; begin ... SetLength(MonTab, 20); MaValeurRetour := FDLLRemplirTableau(MonTab[0]); ... |
Proposer une nouvelle réponse sur la FAQ
Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour çaLes sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2024 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.