""
Version imprimable
""
Essaie comme ceci:
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 //Factory.cpp #include <string> #include "Factory.h" #include "MyQt.h" extern "C" DYNAMIQUE_API LibraryInterface* createInstance() { return (new MyQt()); } extern "C" DYNAMIQUE_API void deleteInstance(LibraryInterface *instance) { delete instance; }
Code:
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 #include <windows.h> #include <iostream> #include "LibraryInterface.h" using namespace std; typedef LibraryInterface* (*POINTEUR_FONCTION) (void); int main(int &argc, char **argv) { HINSTANCE DllInstance; POINTEUR_FONCTION createInstance = NULL; if ((DllInstance=LoadLibrary(TEXT("DllLibrary.dll")))!=NULL) { cout << "Load" << endl; createInstance = (POINTEUR_FONCTION)::GetProcAddress(DllInstance, "createInstance"); if (createInstance != NULL) { cout << "Fonction Load" << endl; LibraryInterface *pMyQt; cout << pMyQt << endl; pMyQt= createInstance(); cout << pMyQt << endl; pMyQt->createWindow(); } else { cout << "Fonction unload" << endl; } //On libère la dll FreeLibrary(DllInstance); }else{ cout << "Unload" << endl; } char pause; cin >> pause; return 0; }
Si "createInstance" n'est toujours pas trouvé, essaie en le précédant d'un underscore dans l'appel à LoadLibrary().
Il est normal le char* dans
?Code:typedef LibraryInterface* (*POINTEUR_FONCTION) (char*);
Le manque de portabilité entre compilateur C++ rend l'utilisation d'API C++ complètement illusoire.
Moi, j'utiliserais une API C avec un void* (un handler) comme premier paramètre qui sera un pointeur sur l'objet.
Résultats :
un complète compatibilité avec tout les compilateurs C/C++,
cloisonnement strict entre les C-Runtime das Dll,
utilisation par des languages non-objet
utilisation de la POO via le handle ( à la Win32)
Rien n'empêche de créer des DLLs cantonnées à un seul projet, tu sais...
De plus, ensuite il y a des standards pour une meilleure portabilité au niveau objet, sous certains systèmes. Sous Windows, il y a COM (Component Object Model), qui marchera quel que soit le compilateur du moment qu'on respecte ses guidelines.
Une API COM est une API "Objet" language agnostique, et pas C++.
Déjà qu'ils ont du mal à gérer des types simples, imagine avec les types template pour la gestion de la durée de vie des références.
Une dll pour 1 projet, autant pas faire de dll. les dll sont faite pour être partagées et évolutives, sinon autant faire des librairies statiques.
On peut toujours les partager entre plusieurs exécutables d'un même projet. C'est le cas à mon boulot, d'ailleurs.
A part obliger tous les exécutable à avoir les mêmes compilateurs et options de compilation ou encore d'être obligé de faire des compromis dans les configurations d'optimisations, je n'y vous aucun intérêt à part une très hypothétique diminution de taille de l'exécutable qui ne compensera pas le manque de performance au démarrage dû aux compromis d'optimisations.
Donc, à part pour montrer que c'est faisable dans un tout petit périmètre sans grands intérêts, à mon avis, cela ne sert qu'à faire trimer les débutants sur une voix sans issue.
Facile quand tous les exécutables sont dans le même projet...
Je ne vois pas ce que tu veux dire pour "compromis d'optimisations"...Citation:
ou encore d'être obligé de faire des compromis dans les configurations d'optimisations, je n'y vous aucun intérêt à part une très hypothétique diminution de taille de l'exécutable qui ne compensera pas le manque de performance au démarrage dû aux compromis d'optimisations.
Là, je suis d'accord, le périmètre est petit, et ça n'est pas d'un grand intéret pour les débutants. La plupart du temps, si on veut un composant réutilisable en C++, autant le programmer directement en COM.Citation:
Donc, à part pour montrer que c'est faisable dans un tout petit périmètre sans grands intérêts, à mon avis, cela ne sert qu'à faire trimer les débutants sur une voix sans issue.
PS: Le projet sur lequel on bosse à mon boulot est basé sur MFC, contient 90 "projets" Visual 6, dont la moitié des applications. Ici, le code est correctement factorisé et réutilisé. Mais quand ça n'atteint pas cette échelle, on peut se limiter aux templates et bibliothèques statiques...
Je pense donc que nous sommes d'accord sur le fait que lesnakman ne devrait pas tenter de faire une API C++.
Pas si facile si nous avons beaucoup de projets dans une solution et que toute ou partie de la solution doit utiliser une nouvelle version du compilateur.Citation:
Facile quand tous les exécutables sont dans le même projet...
A moins d'être bloqué ad vitam aeternam sur une version antédiluvienne de VC6.
Quand on pousse assez loin l'optimisation, on cherche à rapprocher, grâce au profiling, les codes utilisés ensemble, donc plus il y a d'appelants, plus la probabilité d’avoir du code utilisés ensemble par tous les appelants est faible. Il faut faire des compromis pour que tous les appelant est une bonne performance mais pas la meilleur possible pour l'un des appelant. Chose que l'on peut faire pour chaque lib de chaque projet et nom sur les dll partagé par plusieurs projets.Citation:
Je ne vois pas ce que tu veux dire pour "compromis d'optimisations"...
C'est justement qu'en ça atteint une certaine échelle qu'il ne faut pas être lié trop intimement avec le compilateur, pour pouvoir suivre l'évolution du compilateur par exemple.Citation:
Mais quand ça n'atteint pas cette échelle, on peut se limiter aux templates et bibliothèques statiques...
Oui, nous sommes d'accord.
En fait, on a porté toute la solution de VC++6 à VS2005 sans problème.
Mais on n'a pas fait d'optimisation cross-module (ni même de LTCG, c'est une honte).
concernant le typedef LibraryInterface* (*POINTEUR_FONCTION) (char*);
C'était un oubli de ma part, car je compte creer différentes instances par la suite...
Merci pour ces réponses, je vais les lire avec attention.
En faisant un static cast ça marche.
Je suis confronter à un autre problème.Code:pMyQt= static_cast<MyQt* > ( createInstance() );
Je dois pouvoir créer une fenêtre Qt:
Code:
1
2 QApplication app(argc, argv); return app.exec();
Je dois donc insérer ce code dans ma DLL.(MyQt.cpp)
A partir de visual, comment je peux faire?
Il faut que j'utilise QtCore4.dll et QtGui4.dll. Je croyais qu'en les plaçant QApplication serait reconnu mais ce n'est pas le cas.
Quelqu'un a une idée?
Merci
Je ne suis pour ma part pas d'accord avec cette idée. Quand on est sur un environnement où il y a suffisamment peu de compilateurs différents pour qu'on puisse :
- Soit faire une version pour tous les compilateurs/options de compilation majeures (sous visual studio, par exemple, seules 2 options posent souvent problème en pratique)
- Soit imposer aux personnes désirant travailler avec nous d'utiliser les mêmes compilateurs avec les mêmes options
- Soit un mix des deux solutions
Définir une API C++ ne pose pas de problèmes particuliers. Après, le choix DLL/lib statique est autre, et j'avoue réserver les DLL principalement aux systèmes de plug-ins.
Les alternatives ont un coût qui n'est pas négligeable pour un projet :
- Obligation de définir un wrapper autour de l'ensemble des classes d'interface, qui plus est dans un langage pas forcément très bien maîtrisé par les équipes, et souvent très basique.
- Difficultés d'utilisation de ce wrapper, justement parce qu'il est défini dans un langage basique qui ne fourni généralement pas de bons outils de gestion de la mémoire, qu'il a un modèle de gestion d'erreur différent du reste du code,...
- Baisse de performances causées par le wrapper, entre autre par la sérialisation/désérialisation de certaines structures de données qu'il demande.
Heu, mon alternative n'utilise aucun wrapper, est en C, ne propage aucune exceptions, ne definis que des code d'erreur, est compilateur indépendant.
Médinoc...C'est vrai qu'en incluant le MyQt.h , je penses que ça ne respecterais plus le pattern factory.
Si j'ai bien suivi, ta proposition est :
On peut comprendre cette phrase de trois façons :Citation:
Moi, j'utiliserais une API C avec un void* (un handler) comme premier paramètre qui sera un pointeur sur l'objet.
Soit : Tu as une seule fonction qui ressemble à ce que tu indiques, et de l'autre côté, tu vas caster le void* en quelque-chose qui a plus de sens pour l'utiliser. Dans ce cas, on n'est pas indépendant du compilateur, on doit avoir le même avec les mêmes options des deux côtés, ou une ABI compatible, pour que le cast donne quelque-chose qui a du sens.
Ou bien : Tu as toute une série de fonctions en C prenant un premier paramètre un handler, et qui reprennent grosso-modo l'interface publique de l'objet. Là, on devient indépendant du compilateur, mais cette série de fonction constitue un wrapper, et comme il est écrit en C, il est difficile à utiliser correctement (pas d'argument de type string, mais des char*, par exemple).
Ou bien : L'ensemble de ton programme est en C, et là, d'accord, tu n'es plus dépendant du compilateur, et tu n'as pas écrit de wrapper.
Sur l'aspect "ne propage aucune exception", en est tu certain ? Sais-tu que selon les compilateurs/les options de compilation les exceptions C++ peuvent ou pas être propagées à travers une couche C ? De plus, moi, ce qui m'intéresse, c'est justement que ces exceptions soient propagées. Quand j'ai du code qui wrap du code C++, je passe mon temps à catcher les exceptions dans ce code, générer une erreur selon un mécanisme approprié au wrappeur, et restituer les exceptions de l'autre côté du wrapper.
Le sens de ma phrase correspond à la deuxième alternative.
La difficulté d'utilisation est toute relative par rapport aux problèmes de gestion des C-Runtimes, mangling etc...
Le "wrapper" est quasi-transparent au niveau performance.
Une bonne interface est une interface avec peu de méthode donc une surcharge de travail assez faible.
L'inclusion de MyQt.h montre bien que le cloisonnement n'est pas maîtrisé et que le résultat sera une jungle inextricable qui s'écroulera à la première dll qui ne respectera des pré-requis mal maîtrisés.
Les exceptions ne sont pas propagées pareil d'un compilateur à l'autre, elles ne sont donc pas censées être autoriser à franchir la barrière d'une DLL portable.
Bien sûr, dans le cas d'une DLL non-portable comme les DLLs d'extensions MFC dans une solution de 90 projets, cette contrainte est relaxée.
Prenons un exemple concret bien que simpliste : Supposons que la fonction à transmettre ait le prototype suivant :
Je suis très curieux de voir comment tu enrobes simplement et sans problèmes une telle fonction en C.Code:
1
2
3
4
5
6
7
8
9 struct Point { Point(...); private: int x; int y; }; string doSomething(string const &s1, Point p);
En effet. Ce qui signifie qu'il faut d'un côté les attraper toutes, les représenter d'une manière qu'on peut transférer par l'interface d'une DLL portable, puis de l'autre côté relancer une exception de même type. Je ne sais pas écrire ce genre de code avec le C++ actuel, même en admettant le même compilateur avec les mêmes options des deux côtés. Un problème du même genre existe pour transmettre les exceptions entre un thread et le thread qui l'a lancé, et il a demandé des modifications du langage pour y parvenir.
Voir par exemple http://www.open-std.org/jtc1/sc22/wg...006/n2107.html et ses successeurs pour un exposé plus détaillé. Donc, en C++0x, je saurai le faire en admettant un même compilateur avec les mêmes options de chaque côté. Pas dans le cas général.
Dans une API C, il n'y a pas de classe dans les signatures donc pas de string ni de classe public Point.Code:
1
2
3
4
5
6
7
8
9 struct Point { Point(...); private: int x; int y; }; string doSomething(string const &s1, Point p);
On utilise des structures C et pas des structs C++.
Il faut aussi correctement configurer dans les .h définissant les structures les options qui rendront leurs définitions non ambigues comme l'alignement, le padding etc... (dans le fichier et non dans la configuration de la chaîne de compilations)
Donc On remplace
- "struct Point" c++ par une "struct Point" C
- string const &s1 pas LPCSTR
- string par quelque chose en accord avec les paradigmes de partages de données de l'API C.
Donc si je suis bien ton raisonnement, ta fonction aura comme prototype une truc comme :
Et tu auras du :Code:CodeDeRetour doSomethingWrapper(char const *s1, PointWrapper p, char **result);
Avant de faire quoi que ce soit :
- Définir une structure PointWrapper
Côté appelant :
- Copier les données de Point dans PointWrapper
- Récupéré le c_str de la chaîne passée en entrée
- Appeler la fonction de wrapping
Dans la fonction de wrapping :
- Appeler la fonction C++ wrappée
- Allouer une zone de mémoire pour stocker le résultat
- Gérer les possibilités d'échec de cette allocation
- Copier le résultat dans cette zone
- Tout en t'assurant que si une exception est émise par la fonction wrappée, elle sera attrapée et transformée en code d'erreur de retour de la fonction
Au retour de l'appel de la fonction de wrapping :
- Vérifier le code de retour, retransformer le code de retour en exception (chose impossible de manière générique dans l'absolu, voir mon autre post)
- Créer une chaîne à partir de la zone de mémoire remplie par le résultat
- Effacer cette zone de mémoire
Ton wrapping a quand même causé l'écriture de pas mal de code supplémentaire, pour une fonction très simple, et a demandé une allocation mémoire et une copie de chaîne, ainsi que la création d'un wrapper de point et une copie aussi.
C'est ce que je voulais dire en disant qu'il y a un coût. Dans beaucoup de cas, si je travaille sur un environnement où un compilateur est en quasi-monopole, je préfère pour ma part dire : Vous devrez faire votre code avec tel compilateur, et telles options. C'est finalement moins contraignant.
Et c'est à peu près la même chose sous COM, en supposant une compatibilité élevée ("automation-compatible") :
Sachant que l'objet Point ne peut être manipulé que par référence tant qu'on ne met pas les mains dans le cambouis du marshalling. Par contre, dans le cas présent, il peut sans doute être avantageusement remplacé par un tableau (SAFEARRAY) de deux entiers.Code:HRESULT doSomethingWrapper(BSTR s1, IPoint* p, BSTR *result);
...Mais au moins, en COM, on peut quand même avoir une syntaxe pointeur->méthode. C'est déjà ça.
Edit: Et puis, si on se limite au C++, on n'est pas obligé d'être "automation-compatible". Auquel cas, on peut utiliser une structure Point pratiquement telle quelle, définie dans l'IDL.
Sur aucun environnement, il y a un monopole de compilateur, et les options de compilation différentes peuvent rendre les lib incompatibles. L'utilisation de librairies externes peut interdire l’utilisation de certaines options utilisées par le "fameux" environnement.
La structure de Wrapping fait partie du .h de la librairie.
Un simple opérateur de cast entre la classe Point du module appelant vers la structure de Wrapping fera l'affaire pour le passage transparent de l'instance.
Si la classe est bien faite, il n'y aura même pas de recopie des éléments.
Le traitement coté librairie de l'appel est factorisé par l'utilisation d'une MACRO.
Franchement, ce n’est vraiment pas lourd, à moins d'avoir des centaines de structures non opaque et une gestion mémoire bizarre (normalement, c'est plus simple de faire l'allocation chez l'appelant que chez l'appelé).
même layout mémoire entre les membres de la classe et les champs de la structure.
Tu parles donc d'un comportement indéterminé qui a des chances de marcher selon les compilateurs et leurs options. Pour du code se voulant indépendant de ces derniers...
Tout compilateur sérieux a les options de compilations nécessaires pour maitriser complètement le lay-out mémoire d'une structure C.
Si les options sont différentes entres les compilateurs, il suffit de factoriser les divergences dans un fichier d'en-tête et d'utiliser les #ifdef.
Il n'y aura donc aucun souci de portabilité si ce fichier est à jour.