OVH Cloud OVH Cloud

Exporter des symboles pour les nuls ( moi en fait ! )

26 réponses
Avatar
gbaudin
Bonjour,

si je crée une simple dll dans laquelle j'exporte des objets

exemple :

#ifdef TESTDLL_EXPORTS
#define TESTDLL_API __declspec(dllexport)
#else
#define TESTDLL_API __declspec(dllimport)
#endif

typedef std::map<std::string, std::string> MyMap;
class TESTDLL_API CTestDll
{
protected:
MyMap mMap;

public:
CTestDll(void);
void put( const MyMap& _map )
{
mMap.clear();

const long nbrElem = _map->size();
for ( MyMap::const_iterator it = _map->begin(); it != _map->end();
it++ )
{
std::string key = it->first;
std::string value = it->second;
mMap[key] = value;
}
}
};

//////////////////////////////////////////////////////////////

si je fais un exe qui exploite cette dll


#include "stdafx.h"
#include "testDll.h"

int main(int argc, char* argv[])
{
CTestDll dll;
MyMap map;
map["key"] = "value";
dll.put( map );
return 0;
}


Ce petit test crash dans la methode "put", mais je ne suis pas sur d'en
comprendre les raisons.
Est ce que quelqu'un peut m'eclairer ?

Du coup comment modifier mon exemple pour qu'il fonctionne ?

merci.

10 réponses

1 2 3
Avatar
Manuel Zaccaria
gbaudin a écrit:

Bonjour,
Bonjour,



si je crée une simple dll dans laquelle j'exporte des objets


Aïe! c'est mal parti.


exemple :

#ifdef TESTDLL_EXPORTS
#define TESTDLL_API __declspec(dllexport)
#else
#define TESTDLL_API __declspec(dllimport)
#endif

typedef std::map<std::string, std::string> MyMap;


Je vois déjà un problème ici. Ce typedef, en réalité,
peut représenter PLUSIEURS types.
Un MyMap pour chaque dll (debug/release) plus un MyMap
pour chaque exe (debug/release) et que sais-je encore.
Les options de compilations, etc, etc.
Il y a de fortes chances qu'ils soient très différents.

Je les renome MyMapA (dll) et MyMapB (exe) respectivement
pour illustrer le problème.

En compilant la dll, on a :

typedef std::map<std::string, std::string> MyMapA;
class CTestDll
{
protected:
MyMapA mMap;

public:
void put( const MyMapA& _map );
};

et dans l'exe :

#include "stdafx.h"
#include "testDll.h"

/* donc
typedef std::map<std::string, std::string> MyMapB;
class CTestDll
{
protected:
MyMapB mMap;

public:
void put( const MyMapB& _map );
};
*/

int main(int argc, char* argv[])
{
CTestDll dll;
MyMap map;


MyMapB map;

map["key"] = "value";
dll.put( map );


Equivalent à:

dll.put(reinterpret_cast<MapA&>(map));

Boum !

return 0;
}


Ce petit test crash dans la methode "put", mais je ne suis pas sur d'en
comprendre les raisons.
Est ce que quelqu'un peut m'eclairer ?


J'espère que mon explication est suffisament claire.


Du coup comment modifier mon exemple pour qu'il fonctionne ?


Pas simplement en tous cas.. là, je dois partir maintenant.
Mais juste un conseil: utiliser des POD, pas des classes.
(et surtout qu'en plus ce sont des templates ce qui n'aide pas)


Manuel

Avatar
gbaudin
"Manuel Zaccaria" a écrit dans le message de
news: 1bfe5$44bcb9f6$544a9ed1$
gbaudin a écrit:

Bonjour,
Bonjour,



si je crée une simple dll dans laquelle j'exporte des objets


Aïe! c'est mal parti.


exemple :

#ifdef TESTDLL_EXPORTS
#define TESTDLL_API __declspec(dllexport)
#else
#define TESTDLL_API __declspec(dllimport)
#endif

typedef std::map<std::string, std::string> MyMap;


Je vois déjà un problème ici. Ce typedef, en réalité,
peut représenter PLUSIEURS types.
Un MyMap pour chaque dll (debug/release) plus un MyMap
pour chaque exe (debug/release) et que sais-je encore.
Les options de compilations, etc, etc.
Il y a de fortes chances qu'ils soient très différents.

Je les renome MyMapA (dll) et MyMapB (exe) respectivement
pour illustrer le problème.

En compilant la dll, on a :

typedef std::map<std::string, std::string> MyMapA;
class CTestDll
{
protected:
MyMapA mMap;

public:
void put( const MyMapA& _map );
};

et dans l'exe :

#include "stdafx.h"
#include "testDll.h"

/* donc
typedef std::map<std::string, std::string> MyMapB;
class CTestDll
{
protected:
MyMapB mMap;

public:
void put( const MyMapB& _map );
};
*/

int main(int argc, char* argv[])
{
CTestDll dll;
MyMap map;


MyMapB map;

map["key"] = "value";
dll.put( map );


Equivalent à:

dll.put(reinterpret_cast<MapA&>(map));

Boum !

return 0;
}


Ce petit test crash dans la methode "put", mais je ne suis pas sur d'en
comprendre les raisons.
Est ce que quelqu'un peut m'eclairer ?


J'espère que mon explication est suffisament claire.


Du coup comment modifier mon exemple pour qu'il fonctionne ?


Pas simplement en tous cas.. là, je dois partir maintenant.
Mais juste un conseil: utiliser des POD, pas des classes.
(et surtout qu'en plus ce sont des templates ce qui n'aide pas)


Manuel



Je n'aurais pas penser que le type template pouvait provoquer cette erreur
...
Effectivement j'ai fais un essai rapide en remplacant par une liste chainé
et la ca fonctionne.

Merci.

J'ai quand même du mal a comprendre pourquoi map<string,string> n'est pas
considéré de même type dans l'exe et dans la dll ...


Avatar
kanze
Manuel Zaccaria wrote:
gbaudin a écrit:

si je crée une simple dll dans laquelle j'exporte des objets


Aïe! c'est mal parti.

exemple :

#ifdef TESTDLL_EXPORTS
#define TESTDLL_API __declspec(dllexport)
#else
#define TESTDLL_API __declspec(dllimport)
#endif

typedef std::map<std::string, std::string> MyMap;


Je vois déjà un problème ici. Ce typedef, en réalité,
peut représenter PLUSIEURS types.


Oui et non. En tant que type, c'est bien un seul type. En
revanche, il est clair qu'il faut que tous les objets linkés
dans le programme (qu'ils soient linké dynamiquement ou
statiquement) soit compilés avec le même compilateur et les
mêmes options. Qu'on ait une version débug dans un des fichiers
objets et une version optimisée dans un autre, et il y a des
chances que ça ne marche pas.

Aussi, il faut faire gaffe en ce qui concerne les parties qui
dépendent des objets statiques -- dans le cas d'un map, par
exemple, l'allocateur (qui utilise une structure statique pour
gérer la memoire). Il ne faut pas qu'ils utilisent une instance
une fois, et une autre instance une autre fois. Ce qui
correspond à des options par défaut sous Unix, mais qui, d'après
ce que j'ai entendu dire, n'est pas le défaut sous Unix.

(Note bien qu'en général, si j'ai un composant global toto dans
mon objet dynamique, c'est lui que je veux utiliser, même s'il y
a un autre objet global du même nom dans la racine ou dans d'un
autre objet dynamique. En fait, dès que je m'amuse avec les
objets dynamiques, il faut que je réflechisse sur ce qui doit
être partagé entre eux, et ce qui ne le doit pas.)

Un MyMap pour chaque dll (debug/release) plus un MyMap pour
chaque exe (debug/release) et que sais-je encore. Les options
de compilations, etc, etc. Il y a de fortes chances qu'ils
soient très différents.


A priori, dans un cas comme le sien, je dirais non. C'est un
petit exemple ; logiquement, il aurait tout compilé avec les
mêmes options. (Note bien aussi que le problème des options
n'est pas propre au chargement dynamique. J'ai déjà eu des
problèmes parce que certains objets avaient été compilé avec
debug, d'autres non, avec g++ et linké statiquement.)

[...]
Ce petit test crash dans la methode "put", mais je ne suis
pas sur d'en comprendre les raisons. Est ce que quelqu'un
peut m'eclairer ?


J'espère que mon explication est suffisament claire.


Il couvre une des possibilités. Il doit certainement vérifer que
tous ces objets ont été compilés avec les mêmes options. (Il y
a, évidement, les options qu'on peut changer sans problème,
comme le niveau d'avertissements. Mais aller savoir exactement
lesquelles.)

Du coup comment modifier mon exemple pour qu'il fonctionne ?


Pas simplement en tous cas.. là, je dois partir maintenant.
Mais juste un conseil: utiliser des POD, pas des classes. (et
surtout qu'en plus ce sont des templates ce qui n'aide pas)


Je ne sais pas. Je me sers regulièrement des classes assez
complexes dans les objets dynamiques. Typiquement, d'ailleurs,
ce que j'exporte est une classe d'interface : que des fonctions
virtuelles pûres. L'objet dynamique contient une fonction usine
pour les créer, et le code utilisateur ne voit jamais de membres
donnés ni de fonction directement. Dans la pratique, ça permet
de compiler avec des options différentes aussi. (Note qu'avec
cette solution, dans son cas, le map n'apparaîtra que dans la
DLL.)

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
kanze
gbaudin wrote:

Je n'aurais pas penser que le type template pouvait provoquer
cette erreur ...


Si tu as compilé avec les mêmes options, il ne peut pas.

Effectivement j'ai fais un essai rapide en remplacant par une
liste chainé et la ca fonctionne.


Question : où est-ce que tu fais de l'allocation de la
mémoire ?

Un piège assez connu pour les débuttants avec les DLL Windows,
c'est que si on ne prend pas des précautions particulières,
chaque DLL a son propre gestionnaire de la mémoire. Allouer dans
une, et libérer dans l'autre fait boom. Dans ton cas, ça peut
être un problème avec les std::string, créé dans le main, et
libérer dans CTestDll::put. (Mais il peut y avoir d'autres
problèmes aussi.)

Je sais que c'est possible de contourner ce problème, de forcer
tout le monde à utiliser la même instance de la gestionnaire. Ne
travaillant pas sous Windows d'habitude, je ne sais pas la
solution exacte, mais je suis sûr que quelqu'un soit ici, soit
dans un groupe Windows (de préférence... sauf que j'aimerais
aussi voir la solution, et je ne fréquente pas les groupes
Windows:-), pourrait t'aider.

Sinon, il faut éviter de passer des objets qui utilisent de la
mémoire dynamique à travers l'interface (ce qui n'est pas
toujours évident).

[...]
J'ai quand même du mal a comprendre pourquoi
map<string,string> n'est pas considéré de même type dans l'exe
et dans la dll ...


Ils sont considérés de même type. Le problème, c'est c'est là
ton problème, c'est que si tu compiles avec de différentes
options, ils n'ont pas forcément la même implémentation --
c-à-d, en quelque sort, qu'ils ont en fait des types différents,
même si le compilateur les considère de même type.

Mais ce n'est qu'un des problèmes possibles.

En général, le chargement dynamique est une source de problèmes
et d'instabilités. On l'évite quand on n'en as pas explicitement
besoin -- dans un programme applicatif qui ne supporte pas de
plugins, il n'y a que des bibliothèques du système et des
produits tièrce (base de données, etc.) qui doivent être chargé
dynamique. Mettre diverses parties de l'application dans de
différentes DLL's n'a que de désavantages.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
gbaudin
"kanze" a écrit dans le message de news:

gbaudin wrote:

Je n'aurais pas penser que le type template pouvait provoquer
cette erreur ...


Si tu as compilé avec les mêmes options, il ne peut pas.


C'est ce qu'on me confirme egalement ailleurs, c'est deja ca ^^'

Effectivement j'ai fais un essai rapide en remplacant par une
liste chainé et la ca fonctionne.


Question : où est-ce que tu fais de l'allocation de la
mémoire ?


toutes les allocations/desallocations sont faites dans l'exe.
EXE et DLL sont compilés avec le même gestionnaire ...


Un piège assez connu pour les débuttants avec les DLL Windows,
c'est que si on ne prend pas des précautions particulières,
chaque DLL a son propre gestionnaire de la mémoire. Allouer dans
une, et libérer dans l'autre fait boom. Dans ton cas, ça peut
être un problème avec les std::string, créé dans le main, et
libérer dans CTestDll::put. (Mais il peut y avoir d'autres
problèmes aussi.)


la map est alloué dans l'exe et passé a un objet de la dll ( fonction put ),
dans celle ci j'itere a travers la map.
voici un extrait de la methode.
en fait ca plante sur l'operateur ++ de mon iterateur ...

void CTestDll::putContext( const MyMap& _map )
{
const long nbrElem = _map.size();
for ( MyMap::const_iterator it = _map.begin(); it != _map.end(); it++ )
{
std::string key = it->first;
std::string value = it->second;
mMap[key] = value;
}
}


Je sais que c'est possible de contourner ce problème, de forcer
tout le monde à utiliser la même instance de la gestionnaire. Ne
travaillant pas sous Windows d'habitude, je ne sais pas la
solution exacte, mais je suis sûr que quelqu'un soit ici, soit
dans un groupe Windows (de préférence... sauf que j'aimerais
aussi voir la solution, et je ne fréquente pas les groupes
Windows:-), pourrait t'aider.

Sinon, il faut éviter de passer des objets qui utilisent de la
mémoire dynamique à travers l'interface (ce qui n'est pas
toujours évident).

[...]
J'ai quand même du mal a comprendre pourquoi
map<string,string> n'est pas considéré de même type dans l'exe
et dans la dll ...


Ils sont considérés de même type. Le problème, c'est c'est là
ton problème, c'est que si tu compiles avec de différentes
options, ils n'ont pas forcément la même implémentation --
c-à-d, en quelque sort, qu'ils ont en fait des types différents,
même si le compilateur les considère de même type.

Mais ce n'est qu'un des problèmes possibles.

En général, le chargement dynamique est une source de problèmes
et d'instabilités. On l'évite quand on n'en as pas explicitement
besoin -- dans un programme applicatif qui ne supporte pas de
plugins, il n'y a que des bibliothèques du système et des
produits tièrce (base de données, etc.) qui doivent être chargé
dynamique. Mettre diverses parties de l'application dans de
différentes DLL's n'a que de désavantages.


Je ne cherche pas a faire des DLL juste pour decorer les repertoires je te
rassure ;)

Merci pour ces informations.

j'ai encore une petite question :

lorsque je compile la DLL j'ai du coup ce warning

c:sourcestestdlltestdlltestdll.h(45) : warning C4251: 'mMap' : class
'std::map<class std::basic_string<char,struct std::char_traits<char>,class
std::allocator<char> >,class std::basic_string<char,struct
std::char_traits<char>,class std::allocato
r<char> >,struct std::less<class std::basic_string<char,struct
std::char_traits<char>,class std::allocator<char> > >,class
std::allocator<class std::basic_string<char,struct
std::char_traits<char>,class std::allocator<char> > > >' needs to have dll-
interface to be used by clients of class 'CTestDll'

MyMap etant definie comme suis,
typedef std::map<std::string, std::string> MyMap;

je ne peut donc pas le prefixer __declspec( dllexport )

Est ce que cela peut jouer sur mon probleme ?

En fais j'ai pousser le vice a faire ce genre de classe dans ma dll et a
l'utiliser dans mon exe et ca marche alors que je ne precise rien ??!!
pourquoi ?
du coup j'ai un peu de mal a comprendre l'influence du __declspec(
dllimport/dllexport )
Est ce un coup de chance ?

class A
{
public:
void doIt( const std::string& _str){ ::MessageBox( NULL, _str.c_str(),
"TestDll", MB_OK ); }
};

class CTestDll1 : public A
{

public:

CTestDll(void);

void execute( const std::string& _str ){ A::doIt( _str ); }

};


merci.


Avatar
adebaene

gbaudin wrote:

Je n'aurais pas penser que le type template pouvait provoquer
cette erreur ...


Si tu as compilé avec les mêmes options, il ne peut pas.


Effecitvement : Contrairement à ce que dit Manuel, il est parfaitement
possible d'importer/exporter des objets de la STL à travers des
frontières de DLLs, il suffit de prendre quelques précautions....

Effectivement j'ai fais un essai rapide en remplacant par une
liste chainé et la ca fonctionne.


Question : où est-ce que tu fais de l'allocation de la
mémoire ?

Un piège assez connu pour les débuttants avec les DLL Windows,
c'est que si on ne prend pas des précautions particulières,
chaque DLL a son propre gestionnaire de la mémoire. Allouer dans
une, et libérer dans l'autre fait boom. Dans ton cas, ça peut
être un problème avec les std::string, créé dans le main, et
libérer dans CTestDll::put. (Mais il peut y avoir d'autres
problèmes aussi.)

Je sais que c'est possible de contourner ce problème, de forcer
tout le monde à utiliser la même instance de la gestionnaire. Ne
travaillant pas sous Windows d'habitude, je ne sais pas la
solution exacte, mais je suis sûr que quelqu'un soit ici, soit
dans un groupe Windows (de préférence... sauf que j'aimerais
aussi voir la solution, et je ne fréquente pas les groupes
Windows:-), pourrait t'aider.


Tout dépend de l'outil utilisé. Avec Visual Studio, c'est une option
de compilation qui spécifie quelle version de la CRT (et donc du
gestionnaire mémoire) est utilisé : les options /MD (release) et /MDd
(debug) spécifient qu'on utilise la version DLL de la CRT
(msvcrtxx.dll et msvcpxx.dll). Il faut donc compiler tous les modules
ave l'un des ces flags (le même pour tous les modules!). Il faut
ensuite redistribuer la CRT sous forme de DLL avec ton application.

Sinon, il faut éviter de passer des objets qui utilisent de la
mémoire dynamique à travers l'interface (ce qui n'est pas
toujours évident).
Vivement que la norme sous spécifie enfin la notion de module! (et que

les différentes implémentations s'accordent avec cete définition).
Bon d'accord, je rêve un peu tout haut là....

Mettre diverses parties de l'application dans de
différentes DLL's n'a que de désavantages.
Euh... c'est un peu tranché à la hache come opinion non? D'un point

de vue strictement technique, je suis d'accord (dans le cadre du C++
s'entend). Mais en termes de découpage du développement en plusieurs
équipes / maintenance / déploiement de patchs, etc...., c'est plus
discutable...

Arnaud
MVP - VC


Avatar
Loïc Joly

En général, le chargement dynamique est une source de problèmes
et d'instabilités. On l'évite quand on n'en as pas explicitement
besoin -- dans un programme applicatif qui ne supporte pas de
plugins, il n'y a que des bibliothèques du système et des
produits tièrce (base de données, etc.) qui doivent être chargé
dynamique. Mettre diverses parties de l'application dans de
différentes DLL's n'a que de désavantages.


Le problème viens dès que l'on a des plug-ins. Je me retrouve très
souvent dans le cas où j'ai des plug-ins qui utilisent une
infrastructure commune. Et bien, je me retrouve souvent obligé de mettre
cette architecture dans une DLL aussi, car sinon, la mémoire globale (et
donc les singletons) gérée pas cette bibliothèque se trouve dupliquée
par DLL de plug-in, ce qui n'est pas sans poser de problèmes.

Autre cas d'utilisation de DLL : .NET... Il n'y a pas de linker avec ce
framework, et tout passe donc par des bibliothèques dynamiques :(

--
Loïc

Avatar
Manuel Zaccaria
gbaudin a écrit:

Je n'aurais pas penser que le type template pouvait provoquer cette erreur


Ce n'est pas ce que j'ai dit. Avec les templates, le code client a une
dépendance facheuse avec l'implémentation, private ou pas. Ca aide ça ?

J'ai quand même du mal a comprendre pourquoi map<string,string> n'est pas
considéré de même type dans l'exe et dans la dll ...


Oh mais pour le compilateur il s'agît du même type (comme James Kanze l'a
justement souligné) mais quand on ment au compilateur... il se venge.


Manuel

Avatar
Manuel Zaccaria
adebaene a écrit:

Effecitvement : Contrairement à ce que dit Manuel, il est parfaitement
possible d'importer/exporter des objets de la STL à travers des
frontières de DLLs, il suffit de prendre quelques précautions....


Je ne vois nul part dans mon message où j'ai écris que c'est impossible.
J'ai juste dit que ce n'était pas simple. On est bien d'accord là ?

Sinon, il faut éviter de passer des objets qui utilisent de la
mémoire dynamique à travers l'interface (ce qui n'est pas
toujours évident).



Eh oui! Si je passe un std::string de mon application au ma bibliothèque
(DLL/so) et que celle ci en augmente la taille avec un push_back, qui en
est responsable ?

Vivement que la norme sous spécifie enfin la notion de module! (et que
les différentes implémentations s'accordent avec cete définition).
Bon d'accord, je rêve un peu tout haut là....


Amen.


Manuel


Avatar
Arnaud Debaene
"Loïc Joly" a écrit dans le message de
news: 44be857b$0$166$

Autre cas d'utilisation de DLL : .NET... Il n'y a pas de linker avec ce
framework, et tout passe donc par des bibliothèques dynamiques :(


Oui, mais en quoi ca te chagrines, vu que tu n'as pas les problèmes
spécifuqes au C++ dans ce cas là? Sans parler du fait que la frontière de
sécurité et de visibilité que représentent les assemblies .NET est parfois
bien utile

Arnaud

1 2 3