IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Bibliothèques C++ Discussion :

Appel en MS VC++ d'une Fonction en DLL Delphi 6


Sujet :

Bibliothèques C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre confirmé
    Homme Profil pro
    Inscrit en
    Mars 2006
    Messages
    85
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Argentine

    Informations forums :
    Inscription : Mars 2006
    Messages : 85
    Par défaut Appel en MS VC++ d'une Fonction en DLL Delphi 6
    Bonjour,

    Nous envisageons la migration d'un projet avec deux équipes: la couche d'accès DB en une DLL Delphi 6 et la couche GUI en MS VC++.

    Nous avons expérience et produits qui tournent avec DLL en MS VC++ et GUI en Delphi, et bien sur avec les deux composants (Caller + dll) dans le même language.

    Mais faire l'inverse: Appel en C++ et DLL en Delphi6 nous pose de pb.

    Nous avons bien lu:

    http://www.developpez.net/forums/d83...appel-dll-cpp/
    http://www.esanu.name/delphi/DLL/Cal...isual%20C.html

    Par contre nous ne trouvons pas:

    Je t'invite à consulter l'excellent tutoriel d'Olivier Danhan qui traite de toutes les subtilités à tenir compte pour ce besoin très précis.
    http://www.eyrolles.com/Chapitres/9782212111439/19.pdf
    Nous avons crée le *.LIB à partir du DEF (suivant MS MSDN) et aussi à partir del outils des tiers.
    Mais le LIB que nous arrivons à construire ne nous permet pas de résoudre le LINK défaillant.

    Main.obj : error LNK2001: unresolved external symbol "__declspec(dllimport) char * __cdecl GetCurrentUserName(void)" (__imp_?GetCurrentUserName@@YAPADXZ)

    La fonction GetCurrentUserName réponds correctement appellée depuis Delphi.

    Merci pour l'aide,
    Cordialement,

    Horacio

  2. #2
    Expert confirmé
    Avatar de Melem
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Janvier 2006
    Messages
    3 656
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Janvier 2006
    Messages : 3 656
    Par défaut
    Il faut mettre :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    extern "C" __declspec(dllimport) char * __stdcall GetCurrentUserName(void);
    Si GetCurrentUserName utilise la convention d'appel stdcall. Si aucune convention d'appel n'a été donnée, il faut alors remplacer __stdcall par __fastcall ("register") qui est la convention d'appel par défaut utilisée par Delphi.

  3. #3
    Membre confirmé
    Homme Profil pro
    Inscrit en
    Mars 2006
    Messages
    85
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Argentine

    Informations forums :
    Inscription : Mars 2006
    Messages : 85
    Par défaut
    Merci Melem pour ta réponse,

    Le code VC++ (6) d'appel est:

    // This main calls GetCurrentUserName function from DLL in Delphi6
    #include <iostream>
    extern "C" __declspec(dllimport) char * __stdcall GetCurrentUserName(void);

    using namespace std;
    void main()
    {
    char* Name;
    cout << "Prosiga\n";
    Name = GetCurrentUserName();
    return;
    }
    L'erreur obtenue du Linker:

    //Main.obj : error LNK2001: unresolved external symbol __imp__GetCurrentUserName@0
    Le code Delphi de la DLL:

    library GunDll;

    uses
    SysUtils,
    Classes,
    Windows;

    {$R *.RES}

    function GetCurrentUserName() : PChar stdcall;
    const
    cnMaxUserNameLen = 254;
    var
    sUserName : string;
    dwUserNameLen : DWord;
    begin
    dwUserNameLen := cnMaxUserNameLen-1;
    SetLength( sUserName, cnMaxUserNameLen );
    GetUserNameA(
    PChar( sUserName ),
    dwUserNameLen );
    GetCurrentUserName:=PChar( sUserName );
    end;
    exports
    GetCurrentUserName;

    end.
    Cordialement,
    Horacio

  4. #4
    Expert confirmé
    Avatar de Melem
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Janvier 2006
    Messages
    3 656
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Janvier 2006
    Messages : 3 656
    Par défaut
    Bin cela signifie tout simplement que l'entrée __imp__GetCurrentUserName@0 n'existe pas dans le .lib. Comment avez-vous fait pour générer ce LIB ? Normalement, ça se fait avec l'utilitaire LIB fourni avec Visual C++. Je ne connais pas trop les outils Borland/CodeGear. Cet utilitaire suppose cependant que les fonctions dans la DLL utilisent la convention d'appel par défaut des langages C et C++, à savoir cdecl. Donc :

    - Soit tu changes la convention d'appel de la fonction en cdecl.
    - Soit tu laisses la convention d'appel en stdcall, voire en fastcall (register) si tu veux, et lu charges la DLL dynamiquement (donc plus besoin de .lib). Par exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #include <iostream>
    #include <windows.h>
     
    extern "C" {
       typedef char * __stdcall MYFUNC(void);
    }
     
    int main()
    {
        HMODULE hModule = LoadLibrary("mydll.dll");
        if (hModule != NULL)
        {
            MYFUNC * GetCurrentUserName = GetProcAddress(hModule, "GetCurrentUserName");
            if (GetCurrentUserName != NULL)
            {
                std::cout << GetCurrentUserName() << std::endl;
            }
            FreeLibrary(hModule);
        }
        return 0;
    }

  5. #5
    Membre éclairé
    Avatar de gb_68
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2006
    Messages
    232
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Haut Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2006
    Messages : 232
    Par défaut
    Bonjour,
    en fait malgré l'extern "C", le [ame="http://en.wikipedia.org/wiki/Name_mangling"]"name mangling"[/ame] (ou Decorated Names - décoration de noms) n'est pas totalement effacé. Celui du C++ est bien supprimé, mais pas celui du stdcall en C.

    Si en VC++, il est possible de le supprimer complètement lors de l'export d'une fonction en utilisant un fichier de définition (.def), lors de l'importation de symbole, je ne crois pas que cela soit possible (pas à ma connaissance en tout cas ).

    Une solution consiste alors à l'exporter en Delphi avec la décoration de nom attendue par VC++ (comme expliqué dans le lien http://www.esanu.name/delphi/DLL/Cal...isual%20C.html).
    Code Delphi : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    exports
      GetCurrentUserName; // export normal
      GetCurrentUserName name "GetCurrentUserName@0"; // export pour VC++

    Le chiffre après le @ est la taille en bytes des paramètres de la fonction.
    Ce nombre est l'espace que l'appelant doit ABSOLUMENT allouer sur la pile, en poussant les arguments, car l'appelé se chargera lui même les dépiler ; si l'appelant ne respect pas cette taille, le décalage en résultant corrompra totalement la pile.
    En cdecl, c'est l'appelant qui pousse et décharge les paramètres sur la pile, ce qui n'entraine pas de corruption si trop de paramètres sont passés (et ce qui permet notamment le varags du C).

    Un bon outil pour voir ce qu'une Dll exporte est Dependency Walker.

    Sinon il est aussi possible charger dynamiquement la Dll comme l'explique Melem, mais je te déconseille d'utiliser la convention register (par défaut en Delphi) car elle n'est pas supportée en VC++ (elle correspond bien au fastcall de C++Builder, mais pas au fastcall de VC++).

  6. #6
    Membre éclairé
    Avatar de gb_68
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2006
    Messages
    232
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Haut Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2006
    Messages : 232
    Par défaut
    Petite remarque sur ton code :
    Code Delphi : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function GetCurrentUserName() : PChar stdcall;
    const cnMaxUserNameLen = 254;
    var
      sUserName : string;
      dwUserNameLen : DWord;
    begin
      dwUserNameLen := cnMaxUserNameLen-1;
      SetLength( sUserName, cnMaxUserNameLen );
      GetUserNameA( PChar( sUserName ), dwUserNameLen );
      GetCurrentUserName:=PChar( sUserName );
    end;
    sUserName est une variable locale qui sera détruite à la sortie de la fonction, donc renvoyer un pointeur dessus n'est vraiment pas conseillé .
    C'est comme renvoyer le pointeur c_str() d'une std::string locale à une fonction.

    Il existe plusieurs techniques pour contourner ce problème (variable globale modifiée à chaque appel dont renvoie l'adresse, de même mais avec une variable globale de thread, demander un buffer en entrée, ... ou ma préféré : prendre une std::string en paramètre var - si si, c'est possible).

  7. #7
    Expert confirmé
    Avatar de Melem
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Janvier 2006
    Messages
    3 656
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Janvier 2006
    Messages : 3 656
    Par défaut
    Citation Envoyé par gb_68 Voir le message
    Bonjour,
    en fait malgré l'extern "C", le "name mangling" (ou Decorated Names - décoration de noms) n'est pas totalement effacé. Celui du C++ est bien supprimé, mais pas celui du stdcall en C.

    Si en VC++, il est possible de le supprimer complètement lors de l'export d'une fonction en utilisant un fichier de définition (.def), lors de l'importation de symbole, je ne crois pas que cela soit possible (pas à ma connaissance en tout cas ).

    Une solution consiste alors à l'exporter en Delphi avec la décoration de nom attendue par VC++ (comme expliqué dans le lien http://www.esanu.name/delphi/DLL/Cal...isual%20C.html).
    Code Delphi : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    exports
      GetCurrentUserName; // export normal
      GetCurrentUserName name "GetCurrentUserName@0"; // export pour VC++

    Le chiffre après le @ est la taille en bytes des paramètres de la fonction.
    Ce nombre est l'espace que l'appelant doit ABSOLUMENT allouer sur la pile, en poussant les arguments, car l'appelé se chargera lui même les dépiler ; si l'appelant ne respect pas cette taille, le décalage en résultant corrompra totalement la pile.
    En cdecl, c'est l'appelant qui pousse et décharge les paramètres sur la pile, ce qui n'entraine pas de corruption si trop de paramètres sont passés (et ce qui permet notamment le varags du C).

    Un bon outil pour voir ce qu'une Dll exporte est Dependency Walker.

    Sinon il est aussi possible charger dynamiquement la Dll comme l'explique Melem, mais je te déconseille d'utiliser la convention register (par défaut en Delphi) car elle n'est pas supportée en VC++ (elle correspond bien au fastcall de C++Builder, mais pas au fastcall de VC++).
    C'est beau d'avoir frappé tout ça mais ça aurait encore été plus beau si tu ne racontais pas n'importe quoi ...

    1.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    extern "C" {
       typedef char * __stdcall MYFUNC(void);
    }
    Est un typedef. Ce n'est pas une déclaration de fonction. On ne parle même pas de décoration de nom ici. Même l'extern "C" est inutile. J'ai juste mis ça à titre informatif, un rappel que c'est une déclaration de fonctions à interface C.

    2. Pour la décoration des noms, les noms dans les .lib sont toujours décorés, peu importe la manière dont tu as exporté tes fonctions (fichier def ou __declspec) et on n'a aucun contrôle dessus. C'est important parce que les compilateurs doivent connaître les noms que le linkeur doit chercher dans les .lib lorsqu'on déclare une fonction importée. Ce sont les noms dans le fichier DLL qui peuvent ne pas être décorés (possible en utilisant un fichier def lors de la construction de la DLL).

    3. Même si, depuis Delphi, tu exportais la fonction sous un nom identique à ce que VC++ ferait (qui serait, en passant, _GetCurrentUserName@0 (il te manquait l'underscore)), ça ne change rien. Tu devras toujours passer à la prochaine étape : Générer un .lib à partir de la DLL et là, ta petite manip n'aura servi à rien puisque, comme je l'ai déjà dit, l'utilitaire LIB suppose que toutes les fonctions de la DLL utilisent la convention d'appel cdecl. La convention d'appel ne peut donc pas être stdcall si on veut avoir un .lib. Et même si LIB travaillait avec des fonctions stdcall, ça ne servirait toujours à rien puisque ce ne sont pas des noms dans la DLL qu'il faut s'en occuper mais des noms générés dans le .lib, pour que .lib soit soit utilisable avec Visual C++ (mais ça c'est automatique).

    4. En ce qui concerne register et __fastcall, ça c'est vrai. J'ai fait gaffe sur ce point vu que je n'utilise pas trop BCB/Delphi.

    En conclusion, c'est donc établi que :

    a. Soit on change la convention d'appel de la fonction en cdecl.
    b. Soit tu laisses la convention d'appel en stdcall et lu charges la DLL dynamiquement (donc plus besoin de .lib).

    Dans le cas a., la fonction sera importée ainsi :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    extern "C" {
       __declspec(dllimport) char * GetCurrentUserName(void);
    }
    Ca j'ai oublié de le donner en haut ...

    Citation Envoyé par gb_68
    sUserName est une variable locale qui sera détruite à la sortie de la fonction, donc renvoyer un pointeur dessus n'est vraiment pas conseillé
    +1. Changer la variable sUserName en variable globale est déjà une solution rapide. Forum Delphi pour discuter autour de ce code, pas ici svp.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 16
    Dernier message: 15/10/2009, 17h20
  2. Réponses: 3
    Dernier message: 19/10/2008, 20h14
  3. Argument d'appel de procédure à partir d'une fonction
    Par electrosat03 dans le forum VBA Access
    Réponses: 4
    Dernier message: 30/03/2008, 17h33
  4. [langage] appel d'un tableau dans une fonction
    Par donny dans le forum Langage
    Réponses: 11
    Dernier message: 13/11/2006, 16h17
  5. Comment appeler une fonction JavaScript depuis Delphi ?
    Par Alfred12 dans le forum Web & réseau
    Réponses: 4
    Dernier message: 17/06/2005, 18h15

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo