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

 C++ Discussion :

Probléme lors du passage d'un pointeur de vector<string> entre un dll et un exe


Sujet :

C++

  1. #1
    Membre à l'essai
    Homme Profil pro
    Responsable du parc et des réseaux de télécommunication
    Inscrit en
    Mars 2017
    Messages
    20
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Vaucluse (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Responsable du parc et des réseaux de télécommunication
    Secteur : Enseignement

    Informations forums :
    Inscription : Mars 2017
    Messages : 20
    Points : 20
    Points
    20
    Par défaut Probléme lors du passage d'un pointeur de vector<string> entre un dll et un exe
    Bonjour à tous,

    J'ai codé un DLL avec un fonction transformant un fichier en vector<string>, je ramène le pointeur du vecteur créé dans la partie DLL dans le main de mon exécutable windows et j'essaie d'en exploiter les données. J'obtiens des données cohérente (bon nombre de ligne) mais impossible à lire (donnée proche de l’orignal ou BUG pur et simple).

    Je pense que mon problème est lié à un soucis de l'intégrité de la mémoire (ça ou une mauvaise utilisation des pointeurs).
    Je vous explique (enfin je vais essayer surtout..) en détails.

    Coté DLL :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    vector<string>* DllExport parse_file(string mon_fichier)                           //avec "#define DllExport   __declspec(dllexport)" en début de fichier source
    {
    	vector<string> mon_fichier_en_vector;
    	string ligne;
    		fstream fichier(mon_fichier.c_str());  // on ouvre en lecture
    		while(getline(fichier, ligne)) 		
    				{		mon_fichier_en_vector.push_back((ligne));		}
    		fichier.close();
    	return &mon_fichier_en_vector;
    }

    Coté main.exe :

    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
       HINSTANCE DLLHandle;
       DLLHandle = LoadLibrary("main.dll");
       typedef vector<string>*(*Type_parse_file)(string);    //Prototype de la fonction;
       Type_parse_file parse_file;                                     //Nom de la fonction dans mon main
       parse_file = (Type_parse_file)GetProcAddress(DLLHandle,"parse_file");    //on récupére l'addresse du dll chargé en mémoire
     
    //     ...etc....
     
      vector<string> *mymain=parse_file("mon_fichier_a_mettre_en_vector.txt");
    //jusque la c'est bon
      cout << (*mymain).size() << endl;
    //renvoi une taille conforme au nombre de ligne de mon fichier
    //par contre
    cout << (*mymain)[1] << endl;
    //va se mettre a marquer plus ou moins n'importe quoi
    En changeant la valeur de 1 (l'index de mon vecteur) j’obtiens soit un plantage, soit un affichage partielle du contenu de mon fichier mais ne correspondant pas forcément à la bonne ligne, voir plusieurs lignes d'un coup... Le plus curieux c'est qu'a force de tâtonner avec la commande (*mymain)[100].size() (au alentour de la centième ligne a peu prés je sais plus trop) j'obtiens un nombre de caractère sur la ligne correspondant bien a mon fichier de départ, j'ai même pu confirmer en modifiant le fichier et relançant le programme que la valeur changeait aussi. Par contre dés que j’essaie d'en lire le contenu avec cout << (*mymain)[100] << endl; tout plante.

    J'ai pu lire pas mal de truc à propos de fuite mémoire, de la commande malloc et autres problèmes/solution lié à la mémoire en général mais je dois bien avouer que là mes compétences sont bien trop limités et j'ai besoin d'un œil et d'une aide extérieur.

    Je chercherais bien du coté de la sécurisation de l'espace mémoire dans la partie code du DLL mais je ne sais même pas quel mot clé chercher sur le net. Je nage un peu.
    Ndlr : pour le moment j'utilise g++ sous windows.

    Merci

  2. #2
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut,

    Je vais juste te poser deux questions pour essayer de te mettre sur la voie:
    1. Que renvoie ta fonction parse_file
    2. qu'advient-il de la variable locale mon_fichier_en_vector que cette fonction utilise



    Quand tu auras répondu à ces deux questions, tu sauras ce qui se passe, et tu pourras trouver une solution
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  3. #3
    Membre à l'essai
    Homme Profil pro
    Responsable du parc et des réseaux de télécommunication
    Inscrit en
    Mars 2017
    Messages
    20
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Vaucluse (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Responsable du parc et des réseaux de télécommunication
    Secteur : Enseignement

    Informations forums :
    Inscription : Mars 2017
    Messages : 20
    Points : 20
    Points
    20
    Par défaut
    1.elle renvoie un pointeur
    2.elle est 'détruite' une fois la fonction terminé

    Justement je me demande comment faire pour 'présérver' cette espace mémoire là.
    Je sais qu'il est super simple de renvoyé return vector<string> et de ne pas s'ennuyer avec les pointeurs, mais je risque d'aller vers la gestion de fichier de plus en plus gros et ca me semble dommage de 'copier' un vector<string> qui est déjà en mémoire non?

    Et alors la du coup je m'arrete net :
    1.Si je crée mon vector<string> dans le main
    2.Si j'envoi son pointeur à ma fonction dll pour exploitation et remplissage du vector<string>
    3.Je devrais pas avoir de probléme avec la variable vector<string>
    4.J'ai bon?

  4. #4
    Membre à l'essai
    Homme Profil pro
    Responsable du parc et des réseaux de télécommunication
    Inscrit en
    Mars 2017
    Messages
    20
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Vaucluse (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Responsable du parc et des réseaux de télécommunication
    Secteur : Enseignement

    Informations forums :
    Inscription : Mars 2017
    Messages : 20
    Points : 20
    Points
    20
    Par défaut
    Un grand merci à toi koala,
    La méthode fonctionne, je prend le temps de venir poster une réponse propre mardi.
    Je pense que j'avais bien trop le nez dedans pour voir mon erreur qui était donc....
    ....Mauvaise utilisation du pointeur.
    Encore merci et bon weekend.

  5. #5
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par neokal Voir le message
    1.elle renvoie un pointeur
    2.elle est 'détruite' une fois la fonction terminé
    Bien vu
    Justement je me demande comment faire pour 'présérver' cette espace mémoire là.
    Je sais qu'il est super simple de renvoyé return vector<string> et de ne pas s'ennuyer avec les pointeurs, mais je risque d'aller vers la gestion de fichier de plus en plus gros et ca me semble dommage de 'copier' un vector<string> qui est déjà en mémoire non?
    Je ne suis pas sur qu'il sera copié; il se peut -- à vérifier -- qu'il soit "simplement" déplacé
    Citation Envoyé par neokal Voir le message
    Et alors la du coup je m'arrete net :
    1.Si je crée mon vector<string> dans le main
    2.Si j'envoi son pointeur à ma fonction dll pour exploitation et remplissage du vector<string>
    3.Je devrais pas avoir de probléme avec la variable vector<string>
    4.J'ai bon?
    Ce serait un solution, mais elle poserait un autre problème: Tu dois essayer de faire en sorte d'avoir une barrière nette entre ton application et ta dll: l'application gère ses propres ressources, et la dll les siennes.

    Autrement dit :
    • Tout ce qui sort de la dll, l'application n'a pour seul droit de l'interroger, mais, toute modification ne peut être entreprise que par la dll.
    • De même, la dll n'a que pour seul droit que d'interroger ce que l'application lui transmet, mais ne peut en aucun cas le modifier.


    Si ton application transmet, sous une forme ou une autre, un tableau à ta dll pour qu'elle le modifie, tu brises ces deux règles, et cela ne pourra que poser problème par la suite
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  6. #6
    Membre à l'essai
    Homme Profil pro
    Responsable du parc et des réseaux de télécommunication
    Inscrit en
    Mars 2017
    Messages
    20
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Vaucluse (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Responsable du parc et des réseaux de télécommunication
    Secteur : Enseignement

    Informations forums :
    Inscription : Mars 2017
    Messages : 20
    Points : 20
    Points
    20
    Par défaut
    Bonjour à tous,
    Voila pour continuer sur mon idée de la dérniére fois j'ai finalement bricoler ça :

    Coté DLL :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void DllExport parse_file(string mon_fichier,vector<string> *mon_fichier_en_vector)			//avec #define DllExport   __declspec(dllexport)
    {
    	string ligne;
    		fstream fichier(mon_fichier.c_str());  // on ouvre en lecture
    			while(getline(fichier, ligne)) 		
    				{
    						(*mon_fichier_en_vector).push_back(ligne);
    					//	cout << ligne << endl;
    				}
    		fichier.close();
    }		//END parse_file

    et coté main.exe :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
       HINSTANCE DLLHandle;
       DLLHandle = LoadLibrary("main.dll");
          ... etc ...
      //Définition pour parse file,
       typedef void(*Type_parse_file)(string,vector<string>*);
       Type_parse_file parse_file;
       parse_file = (Type_parse_file)GetProcAddress(DLLHandle,"parse_file");
    Bien évidement coté dll j'ai bien la syntaxe
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    #ifdef __cplusplus		
    extern "C" {
    #endif
    J'appel donc ma fonction via parse_file("mon_fichier_a_lire.txt",&mon_vector_string_toto);
    Et ça marche juste impeccablement bien, même si j’entends et je comprend tout à fait :
    Ce serait un solution, mais elle poserait un autre problème: Tu dois essayer de faire en sorte d'avoir une barrière nette entre ton application et ta dll: l'application gère ses propres ressources, et la dll les siennes.

    Autrement dit :

    Tout ce qui sort de la dll, l'application n'a pour seul droit de l'interroger, mais, toute modification ne peut être entreprise que par la dll.
    De même, la dll n'a que pour seul droit que d'interroger ce que l'application lui transmet, mais ne peut en aucun cas le modifier.
    Cependant un nouveau soucis semble se poser pour moi et pas forcément en lien avec ce que je viens de citer:
    L'utilisation du dll obtenu marche juste 'super bien' avec un main.exe compiler sous gcc mais j'ai quelques petits soucis que je ne comprend toujours pas (décidément!) lorsque je cherche à utiliser ce même dll dans un projet winform/c++ dans visual studio.
    Les appels à des fonctions simples ne gérant que des entiers, string (EDIT: ne marche pas pour les strings non plus ) et autre variables ne posent aucun problèmes.
    Par contre dés que je fait intervenir des vector<string> (je n'ai pas encore testé les vector<int>) la c'est plantage (ou non fonctionnement) général.
    J'avoue que je débute en winform et en visual studio plus généralement et je n'ai pas vraiment d'idée sur la "bonne" façon de faire correctement les déclarations.
    Par exemple :
    Coté DLL :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    vector<string> DllExport test (void){                        //avec #define DllExport   __declspec(dllexport)
    vector <string> test;
    test.push_back("this_is_a_test");
    return test;
    }	//END
    Coté MyForm (dans la procédure d'un événement par exemple (pas propre je sais)) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    HINSTANCE DLLHandle;
    DLLHandle = LoadLibrary(L"main.dll");
    typedef vector<string>(*Type_test)(void);
    Type_test test;
    test = (Type_test)GetProcAddress(DLLHandle, "test");
    Que j'appelle donc via la fonction mon_vector_retour=test();
    Me renvoi toutes sorte d'erreur selon l'endroit ou je déclare tout ça et même selon l'endroit ou je déclare std::vector<std::string>mon_vector_retour;

    Voilà je pense que je fait clairement fausse route, mais j'avoue que ma volonté de faire se heurte à la réalité à cause de ma compréhension trop limité des méthode que j'essaie d'utiliser.

    J'hésite sur plusieurs pistes, visual studio n'aime pas les vector, j'ai mal compiler mon dll (j'utilise la commande 'g++ dll.cpp -shared -o main.dll -std=c++11 -static-libgcc -static-libstdc++') ou encor visual studio 'protége' les zones mémoires de ses programmes (ça n'explique pas pourquoi ma fonction test ne marche pas sauf vraiment si j'ai mal déclaré mes variables).

    Bref, merci par avance pour les lanternes..

  7. #7
    Membre à l'essai
    Homme Profil pro
    Responsable du parc et des réseaux de télécommunication
    Inscrit en
    Mars 2017
    Messages
    20
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Vaucluse (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Responsable du parc et des réseaux de télécommunication
    Secteur : Enseignement

    Informations forums :
    Inscription : Mars 2017
    Messages : 20
    Points : 20
    Points
    20
    Par défaut
    Ici-même je viens de tomber sur une info intéressante :
    https://cpp.developpez.com/faq/vc/?p...plusplus-et-VB

    Que puis-je interfacer entre une DLL VC++ et VB ?
    En théorie, seuls les types de base sont passables d'une DLL C++ à VB:
    Mon problème est-il de cet ordre là?
    Pourquoi cette méthode fonctionne t'elle parfaitement avec compilation g++ de mon main.exe en mode console?

    Merci

  8. #8
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 071
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 071
    Points : 12 116
    Points
    12 116
    Par défaut
    Pourquoi cette méthode fonctionne t'elle parfaitement avec compilation g++ de mon main.exe en mode console?
    La réponse est limpide. (quand on sait comment ça marcher)
    Tu n'as pas suivi les conseils très avisés de notre cher @koala01. Et c'est pas une blague.

    Quand vous ne suivez pas les conseils de @koala01 en terme d’étanchéité de la mémoire entre les modules exécutables, vous pouvez esquiver pas mal de problèmes que cela entraine en utilisant les mêmes compilateur/linker/options de compilation pour générer les modules (l'exe et la dll).
    En restant avec la même configuration pour compiler les modules, ils peuvent partager la même C-Runtime, la même C++-Runtime, la même définition des types, les même options de padding, etc...
    Comme ils partagent plein de choses qui ne sont pas standardisés, le manque d’étanchéité de la mémoire ne vous pète pas instantanément à la figure.
    Mais le simple passage de Debug à Release, par exemple, et c'est la fin des haricots et tout part en sucette.

    Donc le "Pourquoi cette méthode fonctionne t'elle parfaitement", c'est juste parce que tu as eu la chance de ne pas modifier les options de compilation et de compilateur entre les générations des modules.

    C'est donc une "solution" très peu fiable et qui réduit énormément l'intérêt d'une Dll.

    Mon problème est-il de cet ordre là?
    Non pas vraiment.
    C'est une source qui datent d'il y a plus de 15 ans et son VB, c'est VB5/VB6, qui n'a pas grand-chose de commun avec VB.NET ou .NET.
    Il ne parle que de passage de type primitifs, pas de passage de pointeurs sur objet (sauf la bidouille pour faire un mapping du layout d'une structure C avec des primitives VB, c'est un grand classique pour l'appel à l'interface Win32, en C, depuis du code VB5/6).
    Et sa réticence devant COM/ActiveX fait bien rigoler, car c'est le format à privilégier quand on fait du VB.NET (aussi en VB5/6, mais ça donnait bien plus de travail, je l'admets).

    Votre mode d'utilisation de la Dll est extrêmement lourde. En utilisant correctement les options/outils offerts par Visual Studio, vous n'auriez qu'à ajouter un #include d'un .h dans le code et ajouter un .lib aux paramètres du projet.
    SVP, n'utilisez plus ces antiquités que sont "LoadLibrary" et "GetProcAddress", quitte à bricoler un .def pour générer un .lib, ou utiliser des générateur de .h et de .lib à partir d'un .dll.
    Mais cela ne réglera pas vos problèmes d'étanchéité de la mémoire.

    L'utilisation du dll obtenu marche juste 'super bien' avec un main.exe compiler sous gcc mais j'ai quelques petits soucis que je ne comprend toujours pas (décidément!) lorsque je cherche à utiliser ce même dll dans un projet winform/c++ dans visual studio.
    Tous ce que vous avez esquivés malgré vous en utilisant "les mêmes compilateur/linker/options de compilation pour générer les modules" et donc partageant plein de trucs pas standards, bin là, vous les prenez directement dans la tronche.
    La Dll générée avec gcc ne partage pas la même C-Runtime que l'exécutable généré par Visual Studio, donc un new/malloc fait dans la dll n'utilise pas le même "code source/layout mémoire" que le delete/free qu'appelle votre exécutable.
    J'espère que vous entrevoyez le carnage que cela induit quand une C-Runtime "A" utilise un chainage des blocs mémoires ou d'alignement de bloc autre qu'une C-Runtime "B" et que vous demandez à la C-Runtime "B" de libérer un truc alloué par la C-Runtime "A".
    Si vous tenez à votre "Design" non étanche, vous devez avoir le "compilateur/linker/options de compilation" pour la génération de votre Dll et de votre exécutable, sinon, il faut rendre étanche votre API entre la Dll et les exécutables "clients" de votre Dll.

    Les appels à des fonctions simples ne gérant que des entiers, string (EDIT: ne marche pas pour les strings non plus ) et autre variables ne posent aucun problèmes.
    Le format d'un entier n'est pas standardisé (taille, little endian vs big endian, alignement, etc...) mais les probabilités que les 2 compilateurs "tombent par hasard" sur la même "définition" n'est pas nulle.
    Et là, vous êtes dans le cas où l'on utilise pas la C-Runtime (pas de new/delete), donc, avec des "définitions" compatibles entre les 2 compilateurs, ça passe.
    Rendre l'API de la Dll utilisable par "tous" les compilateurs, c'est n'utiliser que des types n'utilisant pas de C-Runtime et en mettant tous les moyens disponibles pour que l'interprétation d'un type comme "int" soit toujours la même quel que soit le compilateur.
    On arrive donc souvent à une API C et non C++, car le niveau de définition de l'ABI du langage C est bien plus poussé que celui du C++.

    Maintenant, je pense que vous comprenez que la probabilité qu'un type "std::string" a bien moins de chance d'être compatible.

    J'avoue que je débute en winform et en visual studio plus généralement
    Tout ce que j'ai écrit s'applique aussi bien au C++ standard sous Visual Studio qu'en C++/CLI.
    En sachant que les réglages disponibles pour le Runtime pour C++/CLI sont beaucoup moins nombreux et bien plus éloignés que celui de gcc.
    Donc les probabilités de tomber en marche sont encore plus faible qu'en MSVC++ standard.

    et je n'ai pas vraiment d'idée sur la "bonne" façon de faire correctement les déclarations.
    Voir mon laïus sur la lourdeur et l'utilisation de "LoadLibrary" ci-avant.

    Me renvoi toutes sorte d'erreur selon l'endroit ou je déclare tout ça et même selon l'endroit ou je déclare std::vector<std::string>mon_vector_retour;
    Vraisemblablement parce que le compilateur détecte la catastrophe (les #pragma assert dans les .h peuvent voir pas mal de problèmes potentiels).
    De plus, C++/CLI dispose de plusieurs "contexte" : "unmanaged" et "managed", qui rend possible la cohabitation de plusieurs type de code, avec certains valident dans un contexte mais pas dans l'autre.

    Voilà je pense que je fait clairement fausse route,
    Suivez les conseils de @koala01.

    mais j'avoue que ma volonté de faire se heurte à la réalité à cause de ma compréhension trop limité des méthode que j'essaie d'utiliser.
    On s'est tous pris des murs.

    visual studio n'aime pas les vector,
    Si, il est peut-être comme Cassandre voyant la catastrophe arrivée.

    ou encor visual studio 'protége' les zones mémoires de ses programmes
    What ??? Faut arrêter de fréquenter les forums complotistes.

    (ça n'explique pas pourquoi ma fonction test ne marche pas sauf vraiment si j'ai mal déclaré mes variables).
    Le compilateur, avec ces assertes et ces warning est ton ami, le débuggeur aussi.

  9. #9
    Membre à l'essai
    Homme Profil pro
    Responsable du parc et des réseaux de télécommunication
    Inscrit en
    Mars 2017
    Messages
    20
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Vaucluse (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Responsable du parc et des réseaux de télécommunication
    Secteur : Enseignement

    Informations forums :
    Inscription : Mars 2017
    Messages : 20
    Points : 20
    Points
    20
    Par défaut
    Bonjour et merci pour ces réponses qui m'éclairent beaucoup.
    Je soupçonnais un problème fonctionnel même si je suis loin d'avoir les mots (et les notions) suffisantes pour l'expliquer.

    J'étais parti sur l'idée DLL car j'ai pu lire ça et là qu'un DLL suffisamment bien pouvait être exporter vers toutes sortes de langages et j'avoue que ça semblait plutôt tentant.
    Je comprend basiquement (pour pas dire bêtement) que mon problème est principalement lié au faite que chaque compilateur utilise ses propres runtimes et que mes fonctions qui marchent ont eu la chance de marcher car les 'correspondances' étaient assez simples pour faire que tout cela marche. J'ai bon?
    -> Existe t'il un moyen pour utiliser les mêmes runtimes? Dans la configuration de visual studio par exemple.

    Je tient à préciser que je ne tient pas spécialement à mon design 'non étanche' j’essaie surtout de comprendre la bonne façon de faire (surtout par exemple pour juste faire passer un vector<string> entre programme et DLL depuis deux compilateurs différents).

    SVP, n'utilisez plus ces antiquités que sont "LoadLibrary" et "GetProcAddress", quitte à bricoler un .def pour générer un .lib, ou utiliser des générateur de .h et de .lib à partir d'un .dll.
    J'entend bien mais c'est la principale méthode que je trouve dans mes recherches, quel fonction dois-je utiliser du coup?
    Celle là : #using <MonDll.dll> ?
    Il me semble que pour ça il faut coder sous visual studio et le faire d'une manière bien précises.
    ->Ou vous parlez d'un système à chargement implicite? Aurais-je les mêmes problèmes?

    Du coup si je veux simplement renvoyé un vector<string> comme dans mon exemple (et là si je ne m'abuse respecte les conseils de notre cher koala01 ).
    Celle là, coté DLL :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    vector<string> DllExport test (void){                        //avec #define DllExport   __declspec(dllexport)
    vector <string> test;
    test.push_back("this_is_a_test");
    return test;       
    }	       //END
    Je fait comment?
    Désolé pour mes questions mais j'ai énormément de mal à trouver des informations fiables et précises de ce que je recherche à savoir.
    J'aime trop ce que je 'bricole' pour ne pas faire l'effort d'essayer de comprendre.

    Merci

  10. #10
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 519
    Points
    41 519
    Par défaut
    Pour interfacer deux DLLs de compilos complètement différents, il n'y a pas trente-six choix: Soit l'interface de la DLL est entièrement "C" et standardisée, soit elle utilise un format d'interface plus élaboré mais toujours standard, comme Component Object Model (COM).

    Une interface en "C++ nu" ne marche qu'avec une DLL et un EXE du même compilateur.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  11. #11
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 071
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 071
    Points : 12 116
    Points
    12 116
    Par défaut
    Je soutiens tout ce que dit @Médinoc dans sa réponse.

    J'étais parti sur l'idée DLL car j'ai pu lire ça et là qu'un DLL suffisamment bien pouvait être exporter vers toutes sortes de langages et j'avoue que ça semblait plutôt tentant.
    C'est tout à fait vrai, mais il faut faire attention à faire une Dll correctement, en particulier son "API".
    Elle doit être étanche d'un point de vu mémoire et ne pas avoir d’ambiguïté dans les types utilisées.
    Des choses qu'on fait bien plus facilement en C, ou en C++ mais en exportant selon les conventions C, ou encore en utilisant des framework dédiées comme COM (cf. la réponse de @Médinoc).

    Le problème majeur, c'est qu'un "std::vector<std::string>" est un type standard en C++11, mais il ne l'ai pas dans tout autre langage qui n'en dérive pas, et que son implémentation elle n'est pas standard.
    Donc même avec les mêmes runtimes, ça va merder.
    C'est pour ça que le langage C sert de "lingua franca" car il ne définit que des types simples qui sont facilement "émulables" dans les autres langages et que le layout des types de variables (cf. l'ABI du langage) est fixé sur une machine donnée (mis à part les problèmes 32/64bits et autres menu détails ).
    Pour utiliser votre "std::vector<std::string>", il faut que vous le convertissiez dans un format que VOUS maitrisez complétement au niveau représentation en mémoire, que vous documentiez ce format pour que les utilisateurs d'autres langages puissent faire leurs bidouilles pour comprendre dans leur langage le machin qui traine en mémoire avec votre format.
    Cela doit être étanche d'un point de vue mémoire, cela veut dire que c'est le module qui a fait l'allocation de la mémoire qui doit libérer cette même mémoire.
    Donc, dans votre cas, vous devez "sérialiser" votre "std::vector<std::string>" dans un buffer d'octets selon le format que vous avez définit.
    Si le buffer est fourni par l'appelant, le Dll n'a plus grand-chose à faire (attention à ne pas avoir de pointeur dans le format sérialisé).
    Si c'est la Dll qui alloue le buffer, l'appelant n'a pas à le libérer lui-même mais doit appeler une fonction de la Dll qui s'en chargera.
    Pour simplifier l'utilisation de votre Dll depuis un code C++, vous pouvez fournir dans les .h de votre Dll une class-wrapper full inline qui convertira le contenu du buffer d'octets en "std::vector<std::string>".
    Comme ce code sera compilé avec le code de l'appelant, vous partagerez (la class-wrapper et le code appelant) la même définition de "std::vector<std::string>" donc plus de problème de représentation.
    Mais attention, cette class-wrapper devra respecter les pré-requis d'utilisation du buffer. Donc, par exemple, pas de delete du buffer dans le destructeur du wrapper si le buffer n'a pas été alloué par lui, etc...

    Je comprend basiquement (pour pas dire bêtement) que mon problème est principalement lié au faite que chaque compilateur utilise ses propres runtimes et que mes fonctions qui marchent ont eu la chance de marcher car les 'correspondances' étaient assez simples pour faire que tout cela marche. J'ai bon?
    Oui, mais il est possible de maintenir les correspondances "simples", avec des types simples et des buffers d'octets à base de "void*".
    Et en rendant étanche l'API de la Dll, les différences de runtimes ne posent plus de problème.
    L'utilisation de framework comme COM permettent d'avoir un panel de type de base utilisable bien plus grand, et permet aussi de gérer les transferts de mémoires/variable entre module ainsi que la gestion de type "évolué".
    Mais COM, c'est vieux, c'est compliqué et maintenant largement supplanté par .NET.
    En gros, .NET c'est comme COM mais en bien mieux et bien plus simple, mais ça limite le nombre de langage utilisable (on peut "facilement" faire une implémentation .NET COM compatible pour jouer sur les 2 tableaux).

    -> Existe t'il un moyen pour utiliser les mêmes runtimes? Dans la configuration de visual studio par exemple.
    Oui, mais là, c'est carrément commencer à démonter le réacteur nucléaire du Charles De Gaulle avec un tournevis pour lui mettre à la place une machine à vapeur à base de charbon de bois.
    C'est possible mais pas conseiller.
    A la rigueur, configurer VS pour qu'il utilise gcc plutôt que "cl.exe" (le compilateur de MSVC++) est largement plus faisable.
    Mais c'est un peu comme tout compiler avec gcc.
    Et pour les utilisateurs de votre Dll qui n'utilise pas le C++, ils font comment ?

    Je tient à préciser que je ne tient pas spécialement à mon design 'non étanche' j’essaie surtout de comprendre la bonne façon de faire (surtout par exemple pour juste faire passer un vector<string> entre programme et DLL depuis deux compilateurs différents).
    Le plus simple, c'est de convertir votre "std::vector<std::string>" dans un "void*" (buffer d'octets) où les 4 premiers octets contiendraient la taille de votre vector, et, ensuite, à la queue leu-leu, autant de chaine de caractère au format C (avec des '\0' terminal).
    Le class-wrapper pourra facilement le convertir en "std::vector<std::string>" format client.
    Et pour les clients utilisant un autre langage, c'est pas trop compliqué à décoder.
    Mais attention à la gestion de l'allocation du buffer d'octets.

    J'entend bien mais c'est la principale méthode que je trouve dans mes recherches, quel fonction dois-je utiliser du coup?
    Celle là : #using <MonDll.dll> ?
    Houlà, on passe de l'antiquité à la conquête spatiale.
    C'est le genre de truc qui fonction avec des assemblies .NET.
    Une Dll peut être une assemblies .NET mais il faut le faire sciemment et cela "limite" le type de client potentiel.
    Et il faut utiliser VS pour utiliser #using dans son code, mais bon, les compilations conditionnelles c'est pas la mort.
    Mais il est possible que tous les langages visés disposent d'IDE ayant des fonctionnalités pour intégrer facilement des assemblies .NET, qui sait.

    Juste un .h déclarant l'ensemble fonctions visibles de l'extérieur de la Dll et l'utilisation d'un .lib correspondant à la Dll (VS le génère en même temps que la Dll) permet d'utiliser ces fonctions directement.
    Je pense que pour gcc et autres, on dispose du même type d'outils pour du "chargement statique de Dll".

    ->Ou vous parlez d'un système à chargement implicite? Aurais-je les mêmes problèmes?
    Quels problèmes ? Si vous utilisez des types "simple" et que l'API est étanche, pas de problèmes, normalement.

    Du coup si je veux simplement renvoyé un vector<string> comme dans mon exemple (et là si je ne m'abuse respecte les conseils de notre cher koala01 ).
    Absolument pas.
    Au moment du "return" la C-Runtime de la Dll va construire un clone de la variable locale sur la pile selon la définition de ce qu'est un "std::vector<std::string>" pour la Dll.
    Quand le code passera la main à l'appelant, l'appelant, qui est dans l'exécutable, va utiliser ce clone comme s'il avait été construit selon la définition d'un "std::vector<std::string>" pour l'EXE.
    Imagine l'appel du destructeur de cet objet, construit pas la Dlln mais qui sera détruit par l'exécutable ?

    Je fait comment?
    - Types "simple" + étanchéité mémoire + buffer d'octet avec format EXPLCITE
    - ou utilisation de framework comme COM ou .NET

  12. #12
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 519
    Points
    41 519
    Par défaut
    Au passage, pour info:
    API = Application Programming Interface, ce qui définit en gros les prototypes de fonctions et leur utilité.
    ABI = Application Binary Interface, qui définit non seulement tout ce que l'API définit, mais en plus la façon d'appeler les différentes fonctions à bas niveau.

    Ton problème vient du fait que l'ABI diffère entre les deux compilos et runtimes, bien que l'API soit la même.
    Un code écrit selon les normes de codage de COM aura la même ABI quel que soit le compilateur (tant qu'il supporte ce dont COM a besoin)

    #using et .Net, c'est d'un côté plus simple, de l'autre côté plus compliqué: Si tout est en .Net, c'est plus simple. Si tu as des interactions entre code .Net et code natif, ça devient plus compliqué, surtout si tu veux appeler des assemblys .Net depuis un exe natif (même si ça reste possible, et ça peut même être transparent si entre les deux il y a une DLL en C++/CLI).

    PS: Quand je sérialise quelque chose, je préfère utiliser un unsigned char * (const ou non) plutôt qu'un void* pour indiquer un "buffer de données sérialisées". Et si tu programmes avec l'API Windows, les fichiers d'en-tête contiennent un typedef unsigned char BYTE; qui rend ces déclarations plus simples.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  13. #13
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    Et C++17 introduit enfin un type byte : std::byte.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  14. #14
    Membre à l'essai
    Homme Profil pro
    Responsable du parc et des réseaux de télécommunication
    Inscrit en
    Mars 2017
    Messages
    20
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Vaucluse (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Responsable du parc et des réseaux de télécommunication
    Secteur : Enseignement

    Informations forums :
    Inscription : Mars 2017
    Messages : 20
    Points : 20
    Points
    20
    Par défaut
    Bonjour,
    Vous me répondez plus vite que ce que j'arrive à assimiler les informations
    En tous les cas merci pour ces réponses qui sont je trouve d'une grande précision.
    Je comprend mieux l'univers du DLL et j'en ai aujourd'hui une représentation un peu plus réaliste.
    Donc pour résumer : utiliser des variable simple et respecter l'étanchéité mémoire.
    Si je comprend bien si je code tout en C (avec sérialisation propre en mémoire) mon dll marchera vraiment partout (facilement exportable j'entend)?
    Je doute que le ratio codage/temps aille dans ce sens là pour moi sur ce projet mais en tout cas pour certain problème je pense que ça reste une bonne solution, je sais aujourd'hui grâce à vous comment la mettre en œuvre.
    En tout cas vraiment un grand merci à tous !!

  15. #15
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 519
    Points
    41 519
    Par défaut
    Pour être compatible indépendamment du runtime et du compilateur (et même du langage), sous Windows, une DLL est censée:
    • Avoir une interface "C" et non C++ (types simples, fonctions déclarées extern "C", le plus souvent via une macro)
    • Utiliser la convention d'appel __stdcall.
    • Maintenir l'isolation entre les runtimes:
      • Un FILE* ou un descripteur de fichier peut être valide dans une DLL et invalide dans une autre, il ne faut donc pas les transmettre (les HANDLEs sont OK par contre, vu qu'ils sont gérés par Windows et non par le runtime).
      • malloc() et free() sont gérées par le runtime, donc de la mémoire allouée via malloc() dans une DLL doit être libérée par free() dans la même DLL. Pareil pour new et delete. Pour contrer ce problème, deux solutions:
        • La DLL peut exporter une fonction de désallocation pour tout ce qu'elle alloue (et ne jamais désallouer elle-même un buffer qu'on lui transmet).
        • Ou on peut faire les allocations avec des fonctions gérées par l'OS et non le runtime. La fonction recommandée de nos jours étant CoTaskMemAlloc(), sauf pour les énormes allocations où VirtualAlloc() donnera de meilleures performances.
        COM utilise la première solution pour les objets (détruits par leur méthode Release()) et la seconde solution pour le reste.
    • Utiliser un fichier .def pour que les fonctions soient exportées sous leur nom non-décoré: si tu as une fonction EXTERN_C MADLL_API void STDCALL Toto(INT32), il faut un fichier .def pour qu'elle soit exportée en tant que Toto plutôt que _Toto@4.

    Edit: La remarque de bacelar m'y fait penser, contrairement à l'époque de Windows 95 où un processus 16 bits pouvait utiliser une DLL 32 bits, un tel lien n'existe pas entre 32 bits et 64 bits: Un même processus ne peut avoir que des EXE et DLLs de la même bitness.
    Ce qui signifie que pour compatibilité, tu vas devoir compiler ta DLL pour les deux versions (Visual Studio fait ça facilement, pour les autres EDIs je ne sais pas).
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  16. #16
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 071
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 071
    Points : 12 116
    Points
    12 116
    Par défaut
    Si je comprend bien si je code tout en C (avec sérialisation propre en mémoire) mon dll marchera vraiment partout (facilement exportable j'entend)?
    Non, c'est pas si catégorique.
    D'une, si vous faite en sorte que de l'extérieur, c'est facilement utilisable depuis du code C, vous avez un haut niveau d'inter-opérabilité, car tous les langage, ou presque, on de quoi dialoguer avec une API C.
    C'est l'API qui doit être en C, l'implémentation peut être en n'importe quoi, grâce à l'étanchéité mémoire.
    De deux, si vous faites une Dll 32bits, c'est très compliqué de l'utiliser depuis un applicatif 64bits, etc...

    Donc, c'est pas si catégorique. On fait des choix pour avoir le maximum d'inter-opérabilité avec les clients potentiels.

    Si vous faites que du Windows, avoir une API .NET ne posera pas de problème car tous les langages .NET (C++/CLI, IronPython, F#, C#, VB.NET, COBOL.NET etc...) sont inter-opérable.

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

Discussions similaires

  1. Problème lors du passage de l'API 8 à l'API 5
    Par Ryu2000 dans le forum Android
    Réponses: 0
    Dernier message: 17/05/2011, 10h04
  2. Problème lors du passage de mon site en local
    Par florianjoy54 dans le forum Langage
    Réponses: 2
    Dernier message: 14/03/2010, 13h14
  3. Problèmes lors du passage Oracle 10.2.0.1.0 en 10.2.0.3 ?
    Par MatthieuQ dans le forum Installation
    Réponses: 5
    Dernier message: 30/01/2009, 17h24
  4. Problème lors du passage du focus
    Par bruce207 dans le forum VB 6 et antérieur
    Réponses: 3
    Dernier message: 20/05/2008, 18h19
  5. Problème lors du passage de variable
    Par popo dans le forum Flash
    Réponses: 1
    Dernier message: 14/01/2008, 11h46

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