begin process at 2012 02 11 08:54:24
  Trouver un code source :
 
dans
 
Accueil > 

Tutoriels

 > 

Exécution

 > DLL MULTILANGAGE, GENRE API, SANS SHAREMEM, AVEC POINTEUR ET BUFFER

DLL MULTILANGAGE, GENRE API, SANS SHAREMEM, AVEC POINTEUR ET BUFFER


 Information sur le tutoriel

Note :
Aucune note

 Description

Avant mieux, comment faire passer une chaîne d'une dll vers un exe.

Tutorial

{

Si vous préférez le Delphi au Français, le code est dessous !

ShareMemne sera pas utilisée (L'aide en ligne précise qu'elle estnécessairedans la close uses de l'exe, ce qui peut être délicat quandl'EDI d'unautre langage n'a pas de close uses).

Le passage d'Integer et detype énumérés ne semble pas poser deproblèmes, mais en ce qui concerneles String, c'est une autre histoire.

Comme on n'utilise pas ShareMem, le choix se reporte sur les pointeurs.

Le problème est de s'assurer que les pointeurs continuent de pointer sur quelque-chose.

En effet, l'erreur classique consiste à renvoyer un pointeur sur une variable locale à la fonction...

Renvoyerun pointeur sur une variable globale de la dll est une méthodequifonctionne dans certains cas, mais cela me parait très risqué engénéral.

Laconclusion est que les pointeurs doivent pointer sur deschaînesdéfinies dans l'exe, et donc que l'on peut parfaitementtransmettretous les paramètres de type PChar comme const.

Parce que même si la chaîne qui se trouve à l'adresse change, l'adresse ne change pas.

Unproblème possible est que la chaîne renvoyée n'est pas forcément delamême taille que la chaîne qui a initialisé le pointeur, ou quelamémoire allouée pour contenir le résultat soit insuffisante.

Et comme de par hasard, vous constaterez que je me rapproche bizarrement de quelque chose de connu...

Les API utilisent des buffers pour renvoyer des String, dont la taille est aussi spécifiée dans les paramètres...

Evidement,si la chaîne est initialisée avec des espaces, elle seraterminée par un0 terminale et la dll peut donc facilement récupérer lataille de lachaine tampon.

Mais il est certainement plus rapide d'allouer (New et Dispose sous Delphi), et non d'initialiser.

Cependant, la syntaxe des API, comme celle que je présente, est assez contraignante.

Il faut spécifier une taille de buffer, et il n'est pas toujours évident de prévoir la longueur de ce que la dll vat renvoyer.

Lamémoire est très mal exploitée: une partie est allouée sansêtreutilement utilisée (Si le tampon fait 1000 caractères et la chaînearenvoyer en fait 2, vous saisissez la perte).

Et la fonction nerenvoie pas la chaine comme résultat, mais commeparamètre ce qui obligeà attendre la ligne suivante pour l'utiliser.

Evidement, on peut faire passer le même pointeur que celui de l'argument tampon comme résultat de la fonction.

Apparement,ça marche en VB et en Delphi, mais perso, je préfère fairerenvoyer unlong, comme les API, et de m'en servir comme code d'erreur.

(Même pour les routines qui renvoient de long, j'ai décidé d'utiliser un buffer).

En contrepartie, j'alourdis l'utilisation de mes fonctions, alors ne suivez pas forcément mon exemple...

J'ai déclaré les arguments comme String dans les déclaration d'appel sous Delphi (De même que sous VB).

Celapermet de définir des valeurs par défaut pour les chaînes, etpermetdonc plus de souplesse lors de l'emploi d'arguments optionnels.

On fait passer des paramètres string const qui sont en fait modifiés, lol.

ce tuto sera remis a niveau très régulièrement (du moins pendant quelque mois après le 12.08.05).

J'ai bien sûr automatisé le système de conversion 'déclaration dans l'interface de la dll' -> 'Déclaration dans l'exe'.
}

//C'est juste l'API InputQuery encapsullée de manière a ce qu'elle renvoie une chaîne vide si l'utilisateur clique sur annuler.
//Je n'ai pas encore implémenté l'utilisation de lpEX qui sera une chaîne de paramètre pour la dll.
//nSize n'est pas encore utilisée non plus (Je crois qu'il empèchera une faille manifique de type Buffer Overflow).
//Il ne faut pas oublier la close external.


//Routine de la dll:

functionEXsup_InputQuery(constlpBuffer: PChar; const nSize: Integer; constlpEX: PChar; constlpPrompt: PChar; const lpCaption: PChar; constlpDefaultText: PChar):Integer; stdcall;
var
  bolResult: Boolean;
  strText: String;
  I: Integer;
begin
  strText:= lpDefaultText;
  bolResult:= InputQuery(lpCaption, lpPrompt, strText);
  if bolResult = false then strText:= '';
  EY_PCharOutput(lpBuffer, nSize, PChar(strText));
  Result:= 0;
end;

//Routine de la dll de remplissage du buffer:

function EY_PCharOutput(const lpBuffer: PChar; const nSize: Integer; const lpOutput: PChar): Integer;
var
  I: Integer;
  strOutput: String;
begin
  strOutput:= String(lpOutput);
  I:= -1;
  for I:= 0 to Length(strOutput) - 1 do
    lpBuffer[I]:= lpOutput[I];
  lpBuffer[Length(strOutput)]:= #0;
end;

//Déclaration d'appel de l'exe Delphi:

functionEXsup_InputQuery(const lpBuffer: String; const nSize: Integer; constlpEX: String; constlpPrompt: String= ''; const lpCaption: String= '';const lpDefaultText:String= ''): Integer; stdcall; external'EX_Support.dll';

//Emploi dans l'exe Delphi:

type
  TStr1000 = array[0..1000] of Char;

procedure TfrmMain.btnTest1Click(Sender: TObject);
var
  lpBuffer: ^TStr1000;
begin
  New(lpBuffer);
  EXsup_InputQuery(String(lpBuffer), 1000, lpEX, 'Prompt', 'Caption', '123456');
  Application.MessageBox(PChar(lpBuffer), 'Caption');
  Dispose(lpBuffer);
end;

//Déclaration de l'exe VB6:

PublicDeclareFunction EXsup_InputQuery Lib "EX_Support.dll" (ByVal lpBufferAsString, ByVal nSize As Long, ByVal lpEX As String, OptionalByVallpPrompt As String = Empty, Optional ByVal lpCaption As String =Empty,Optional ByVal lpDefaultText As String = Empty) As Long

//Emploi dans l'exe VB6:

Private Sub Command1_Click()
Dim strEX As String
Dim strBuffer As String

strBuffer="                        "
strEX = "AA"
Call EX_Support.EXsup_InputQuery(strBuffer, 1000, strEX, "Prompt", "Caption", "151")
MsgBox strBuffer
End Sub

 Historique

18 octobre 2005 18:56:51 :
Commmentaires pas passés.
24 octobre 2005 09:55:29 :
pfff....

Commentaires

Commentaire de Forman le 27/05/2006 10:57:00

Salut, je viens de lire en passant, et j'aimerais ajoutter quelques précisions:

1/ Concrètement, d'où vient le problème?
Cette question en entraine une autre: qu'est-ce qu'une DLL concrètement? Lors du chargement (statique ou non) d'une DLL, un certain espace mémoire est alloué dans le même espace que le processus qui charge la DLL. Le code-source de celle-ci est ensuite "mappé" comme le code-source d'un exe et sa fonction Init est appelée (comme l'entrypoint d'un exécutable "standalone" classique).

En fait, tout se passe à quelques détails près comme si la dll était un 2ème processus pour lequel l'accès à la mémoire cliente peut se faire directement depuis l'exe sans utiliser Read/WriteProcessMemory. Le point de vue est le même depuis la dll vis-à-vis de l'exe (d'ailleurs, il est tout à fait possible de mettre une clause exports dans un exe, et de faire appeler GetProcAddress depuis la dll vers le handle de l'exe pour charger des fonctions exportées par l'exe dans la dll! c'est à dire le contraire de ce qu'on fait habituellement)

J'ai écrit au paragraphe précédant "à quelques détails près" et c'est ceux-ci qui posent problème. En effet, les fonctions bas niveau d'allocation de la mémoire (GetMem, FreeMem ReallocMem) sont par défaut associés à une table liée à l'exécutable dans lequel elles sont appelées. Ca signifie que normallement, toute allocation de mémoire par GetMem dans un module doit être libérée dans LE MEME module par FreeMem (idem pour ReallocMem). Sinon, c'est des "invalid pointer operations" et "read/write exception @ 0x........".

Concrètement que fait BorlandMM pour pallier à ça? Pas grand chose, il faut savoir que Delphi expose un genre de classe dans System.pas qui contient le gestionnaire de mémoire:

  TMemoryManager = record
    GetMem: function(Size: Integer): Pointer;
    FreeMem: function(P: Pointer): Integer;
    ReallocMem: function(P: Pointer; Size: Integer): Pointer;
  end;

BorlandMM.dll se contente (à peu de choses près) de partager le même TMemoryManager pour tous les modules en mémoire qui utilisent ShareMem.pas.

Effectivement, le type de données qui risque le plus de poser problème est les chaines de caractères. Si on fait '123'+'456' des appels cachés à GetMem  et autres sont effectués automatiquement par Delphi. Et si la chaine '123' avait été à l'origine allouée dans une DLL et que le code est exécuté dans un exe, boum le programme plante.

2/Les variables locales
Je ne suis pas tout à fait d'accord avec ce que tu dis pour les variables locales. En effet, si tu regardes la plupart des codes qui utilisent des API windows, ils passent en argument à ces API des variables locales. Or, ces API ne sont rien d'autres que des DLL. Tu peux tout à fait passer des variables locales à une DLL, comme plus haut il faut juste que la DLL ne touche pas à l'allocation de mémoire de ces variables.

3/Les objets
Lors de la création et de la destruction d'un objet, il y a des allocations/désallocations de mémoire (à travers les méthodes NewInstance et FreeInstance.) Lorsque j'écris:

  o:=TObject.Create;
  o.Destroy;

il y a eu 2 appels à GetMem et FreeMem. Donc LES OBJETS CREES DANS UNE DLL DOIVENT ETRE DETRUITS DANS LA MEME DLL (sinon crac boum ça plante comme pour les chaines de caractères).
En fait ce que je dis n'est pas tout à fait vrai. Si le destructeur de l'objet a été surchargé, il est en effet possible que le code de libération de la mémoire s'exécute dans la DLL malgré tout. Mais ça reste une solution à éviter.

Une bien meilleure solution pour ne pas avoir à se soucier de ça est d'utiliser des interfaces avec reference-counting (AddRef et Release) qui en plus sont gérées automatiquement par Delphi.

4/ Les exceptions
On imagine le cas suivant:
la Dll:

  function Divide(x,y:Integer):Integer;
  begin
    Result:m=x div y;
  end;
  exports Divide;

L'exe:
  function Divide(x,y:Integer):Integer;external 'libbuggy.dll';

  function IsZero(n:Integer):Boolean;
  begin
    try
      Divide(1,0);
      Result:=False;
    except
      Result:=True;
    end;
  end;

Cet exemple (assez stupide j'en conviens) permet de tester si un nombre est nul, en essayant de diviser 1 par ce nombre. S'il se produit une exception, c'est que le nombre était nul (donc on retrourne True) sinon c'est que le nombre n'était pas nul (False). Or, il va se passer 2 choses avec ce code:
-Premièrement, malgré le try...except, si on donne un nombre nul la boite de message d'erreur (LibBuggy.dll has raised an EDivisionByZero exception ou un machin du même genre) va quand même s'afficher.
-Deuxièmement, la fonction IsZero va toujours retourner False!

Pourquoi? Tout simplement parce que le gestionnaire d'exceptions vectorielles n'a qu'une pile par module. Concrètement, ça signifie que même si une fonction appelée dans une DLL déclenche une exception, du point de vue de l'exe tout se passe comme s'il n'y avait jamais eu d'erreur. C'est pour ça que la plupart des API utilisent un résultat de type booléen pour déterminer s'il y a eu une erreur ou non.

La règle pour les exceptions dans les DLL est que LES EXCEPTIONS DECLENCHEES DANS UN MODULE DOIVENT ETRE GEREES DANS CE MEME MODULE CAR LES AUTRES MODULES N'EN SONT PAS AVERTIS.

Voilà, bonne continuation!

Commentaire de rt15 le 29/05/2006 10:16:06 administrateur CS

Salut Forman, et merci pour ces précisions sur mon tuto qui ressemble pas à grand chose.

Concernant le dernier point sur les exceptions, tu viens de me faire palir ! J'ai en effet une appli qui récupère tout un tas d'erreur survenant dans une dll (Sans problème jusqu'à présent...)

Je vais étudier ça plus en détail.

Pour les variables locales, je parlais des variables locales à la fonction appelée, qui sont théoriquement détruites à la fin de l'execution de celle-ci (Si rien n'est réécrit à leur adresse, elles "reste toujours là", mais c'est juste un espoir). Les variables locales de la fonction appelante sont tout à fait utilisables.

Commentaire de rt15 le 30/05/2006 10:13:49 administrateur CS

Pour les exception -> J'ai fait des essais à avec un code similaire au tiens, en exploitant la dll à partir de Delphi et même de VB6.

L'exception s'est toutjours propagé de manière correcte, y compris pour le VB6 qui me signal une erreur de division par zéro parfaitement gérable.

L'aide de Delphi accorde une page sur le sujet, et celle-ci est on ne peut peut plus explicite, les exception ayant lieu dans les librairies sont gérables à partir de l'executable appelant.

Je vais essayer précisément ton code. Il y a une faute de frappe (Result:m=), donc je suppose que tu ne l'as pas testé ?

Commentaire de Forman le 30/05/2006 11:07:33

Je n'ai pas testé ce code-là en particulier, mais j'en ai testé d'autres similaires. Effectivement, si les 2 exécutables (exe+dll) sont codés en Delphi (et peut-être même qu'il faut que ce soit la même version de Delphi) tout se passe bien au niveau de la propagation d'erreur en général.

Mais je suis tombé une fois sur un cas où ça ne fonctionnait pas. Je crois me souvenir que c'était lors de l'appel de l'entrypoint dans la DLL, la méthode DllProc. Celle-ci déclenchait une exception banale, mais qui n'était pas propagée.

Dans tous les cas le passage de l'aide auquel tu fais référence précise:
"Generally, you should not let exceptions escape from your library."

Et il précise aussi que pour que les erreurs soient propagées, il faut de toute façon utiliser SysUtils dans l'exe (je t'accorde que c'est le cas de la plupart des applications).

Commentaire de Forman le 30/05/2006 11:08:41

Si tu veux faire un test, essaie de compiler la dll avec VB6 et l'exe en Delphi ou l'inverse

Commentaire de rt15 le 30/05/2006 18:30:32 administrateur CS

C'est sûr que ce n'est pas très propre de laisser échaper des exceptions d'une dll... (Dans mon cas, je ne l'ai fait que pour stabiliser une appli utilisant une dll quelque peu instable.)

J'ai donc essayé l'autre sens (exe en Delphi/dll en VB6). Celui-ci marche aussi ! Mais différement. VB6 ne permet que de faire des librairies COM. La convention d'appel safecall est sensé gérer les erreurs.

Je récupère donc une exception OleException (ou qqch comme ça), avec le message "Division par zéro" (ou qqch comme ça aussi).

C'est quand même sympatoche cette compatibilité à peu près totale entre deux langages quand même particulièrement différrents !

Commentaire de rt15 le 30/05/2006 18:30:33 administrateur CS

C'est sûr que ce n'est pas très propre de laisser échaper des exceptions d'une dll... (Dans mon cas, je ne l'ai fait que pour stabiliser une appli utilisant une dll quelque peu instable.)

J'ai donc essayé l'autre sens (exe en Delphi/dll en VB6). Celui-ci marche aussi ! Mais différement. VB6 ne permet que de faire des librairies COM. La convention d'appel safecall est sensé gérer les erreurs.

Je récupère donc une exception OleException (ou qqch comme ça), avec le message "Division par zéro" (ou qqch comme ça aussi).

C'est quand même sympatoche cette compatibilité à peu près totale entre deux langages quand même particulièrement différrents !

Commentaire de pey le 22/06/2006 08:07:54

Pour l'utilisation des DLL j'achoppe sur l'exemple suivant:
Une DLL en DELPHI 7+ un appel depuis EXCEL.
Le test de l'appel de la DLL depuis Delphi ne pose aucun problème.
Par contre l'appel depuis Excel me donne des valeurs (très) fantaisistes:
Texte de la DLL:

{library CalcVal_prj;

uses
  SysUtils,Classes;

function CalcVal(a,b:integer):double;stdcall;
begin
   CalcVal:=(a+b);
end;
exports Calcval;
begin
end.}

Texte de la macro Excel:
{Public Declare Function CalcVal Lib "D:\TestDll\CalcVal_prj.dll" (ByRef a, b As Long) As Double

Sub EssaiDll()
  AppelleDLL = CalcVal(2, 3)
End Sub}

Merci de me donner votre avis


JPey

Commentaire de rt15 le 22/06/2006 11:18:14 administrateur CS

Intéressant. Je vais essayer.

Déjà, peut être que :

function CalcVal(var a,b:integer):double;stdcall;

marcherait mieux.

Ou alors plus logiquement dans le cas présent :

function CalcVal(const a,b:integer):double;stdcall;

Public Declare Function CalcVal Lib "D:\TestDll\CalcVal_prj.dll" (ByVal a, b As Long) As Double

Commentaire de rt15 le 22/06/2006 13:02:42 administrateur CS

Simple petite erreur de syntaxe dans les arguments de VB :

En effet :

(a, b As Long) <=> (a As Variant, b As Long)

Ce qui n'a aucune chance de donner les résultats escomptés...

Il faut écrire :

(a As Long, b As Long)

(Rappel ByRef <=> ne rien mettre, passage par adresse)
(ByVal <=> Passage par valeur)

Commentaire de pey le 23/06/2006 16:02:43

Effectivement la méthode fonctionne.
Avec mes remerciements

JPey

Commentaire de otavioreis le 18/04/2007 15:08:04

J'ai trouvé très intéressant cette discution. J'ai un problème pareil. J'ai crée une DLL en Delphi et le test de l'appel de la DLL depuis Delphi ne pose aucun problème.
Par contre l'appel depuis Excel ne marche pas.  

Si un jour quelqu'un passe par ici et arrive à declarer et à appeller sur le VBA d'Excel cette fonction ci-dessous, s.v.p., indiquez moi le chemin.

function NU: pchar; StdCall;
Var
  Buffer : array[0..255] of char;
  BufferSize : DWORD;
begin
  BufferSize := sizeOf(Buffer);
  GetUserName(@buffer, BufferSize);
  GetMem(Result, BufferSize);
  StrCopy(Result, @Buffer);
end;

Merci
Otavio Reis

Commentaire de rt15 le 19/07/2007 11:02:08 administrateur CS

Salut,
Désolé de te répondre si tard...
D'une part, excel ne prend que le stdcall en convention d'appel, d'autre part, tu vas au devant de grand ennuis avec une allocation comme celle-là.

Il faut faire l'allocation dans l'appelée, ou allouer la chaîne dans Delphi avec SysAllocString. Les chaînes de Delphi et de excel sont très différentes.

 Ajouter un commentaire




Nos sponsors


Sondage...

Comparez les prix

CalendriCode

Février 2012
LMMJVSD
  12345
6789101112
13141516171819
20212223242526
272829    

Consulter la suite du CalendriCode

Photothèque

 
Développement réalisé par Nicolas SOREL (Nix) avec l'aide de : Cyril DURAND et Emmanuel (EBArtSoft), Merci à Vincent pour ses précieux conseils.
CodeS-SourceS.com© Toute reproduction même partielle est interdite sauf accord écrit du Webmaster
CodeS-SourceS.com© est une marque déposée tous droits réservés

Google Coop CodeS-SourceS Google Coop CodeS-SourceS
Temps d'éxécution de la page : 0,296 sec (3)

Nous contacter | Annoncer sur CodeS-SourceS | Mentions légales