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.

6 réponses

1 2 3
Avatar
Loïc Joly
kanze wrote on 20/07/2006 11:55:

je n'essaierais jamais de passer le moindre template entre 2 codes
distincts, c'est perdre son temps et finalement inefficace (par
comparaison à 2 méthodes foo(char*) et foo(wchar_t*) plus pereines par
exemple).



Déjà, là, on passe par des pointeurs, donc risque de corruption mémoire.
Rien ne t'empêche de fournir une interface foo (std::string ) et
foo(std::wstring). Il faut juste savoir que ton client devra utiliser le
même compilateur avec les mêmes options.

Ensuite, quand on ne doit fournir une interface fonctionnant avec n
types, n borné et connu par le développeur de la bibliothèques, d'accord
on peut fournir n interfaces. Mais quand ce n'est pas le cas ? De ce
point de vue, que les templates soient compile-only est une limitation
(qui apporte bien d'autres avantages par ailleur).

--
Loïc

Avatar
Sylvain
Loïc Joly wrote on 21/07/2006 08:04:

Déjà, là, on passe par des pointeurs, donc risque de corruption mémoire.


pire, c'est du code ! donc risque de corruption totale.
y'a qlq chose qui empèche de faire des anneries avec c_str() ?

Sylvain.

Avatar
gbaudin
Pour information,

il y a pas mal d'explication ici sur le fait d'exporter des classes STL
template a travers une interface de DLL

http://www.unknownroad.com/rtfm/VisualStudio/warningC4251.html

merci pour vos reponses :)
Avatar
kanze
Loïc Joly wrote:

[...]
Je ne suis pas sûr de comprendre. Sous Unix, et sous les Windows
classiques pré-.NET, il n'y a pas de bibliothèques dynamiques ;
malgré leur nom, une DLL se comporte comme un fichier objet en
partie pré-linké.


Par focément. On peut se lier statiquement avec une DLL, par
l'intermédiaire par exemple d'un .lib d'import, mais on peut
aussi s'y lier dynamiquement (avec des fonction LoadLibrary,
GetProcAdress...).


Je ne connais pas Windows très bien. Est-ce que tu veux dire
qu'on peut se servir d'une DLL soit comme une bibliothèque
statique (linké statiquement, avec incorporation uniquement des
composants utilisés), soit comme objet dynamique ? Ou est-ce
que tu parles plutôt de la distinction entre une édition de
liens dynamique implicite ou explicite ?

Tel que je le comprends (et je suis certain que c'est toujours
le cas sous Unix), quand tu linkes dynamiquement, c'est toujours
du tout ou rien : toute la DLL ou le SO devient partie de ton
programmer, et non seulement l'ensemble de composants minimum
qui sont nécessaires pour résoudre les externes non-résolus. Et
pour moi, c'est ça, la différence entre un fichier objet (dont
l'incorporation est tout ou rien) et un fichier bibliothèque
(qui se décompose en composants, chacun qui n'est incorporé que
si nécessaire).

Et il faut bien quelque chose du genre d'un
éditeur de liens pour les construire. (Sous Unix, c'est bien
l'éditeur de liens classique, ld, qui le fait. J'ai l'impression
que c'est aussi ce qui se passe quand je donne l'option /LD à
cl : il invoque link avec l'option /DLL.)

Ou est-ce que .NET fonctionne un peu comme Java, où les
fichiers .jar sont réelement des bibliothèques, et que les
composants en sont réelement extraits et linkés lors de
l'exécution, sans jamais avoir été linkés entre eux avant ?
(C'est un des grands défauts de Java, qui fait qu'on ne peut
pas s'en servir dans certaines applications, où la sécurité
ou la fiabilité sont des critères importants.)


Je ne connais pas vraiment Java, aussi il y a des chances que
je réponde à côté.

Les DLL externes sont utilisée à l'exécution, bien évidemment,
mais aussi à la compilation, où elles ont pour seul(*) but de
permettre par de l'introspection de publier leur contenu,
remplaçant ainsi les .h.


Tu parles ici de Java ? Java, en soi, n'a pas de DLL (à part
JNI -- mais ça, c'est exprès pour accéder aux parties non en
Java). Il a des fichiers « .class », et chaque fichier class
contient exactement une seule classe. Ensuite, le « class
loader » se sert aussi des fichiers .jar. C'est surtout avec
les jar qu'on difuse les programmes, bibliothèques, etc. Mais un
fichier .jar, ce n'est pas très différent d'un fichier .a
(généré par l'utilitaire ar) sous Unix : c'est une collection
de fichiers, avec une indice et parfois des fichiers en plus
avec des méta-informations (signature, etc.) Quand tu charges
une classe en Java (en général, implicitement, parce que ton
code s'en est servi), le class loader cherche un fichier .class
avec le nom dans un certain nombre d'endroits bien précis
(modifié par la variable d'environement CLASSPATH), qui peuvent
être soit des répertoires, soit des fichiers .jar. Et il ne
charge que le .class voulu, non tout le fichier .jar.

C'est donc un comportement très proche de celui d'un éditeur de
liens classiques avec une bibliothèque. Et tout à fait autre
chose que l'édition de liens dynamique en C++.

Et il est aussi possible de se lier avec des DLL.NET dont on
ne connait rien a priori, puisqu'on peut en obtenir tout un
tas d'information au run-time (genre : Bon, donne moi toutes
les classes qui implémententtelle interface et on un
constructeur sans paramètre, puis contruit moi une instance de
celles-ci).

Le problème est que les modules (les DLL) définissent la
granularité de base des certains points du framework, comme le
partage de code, un certain aspect de visibilité,... Ce qui
incite presque dans certains cas à avoir un module par classe.
Et là, ça fait beaucoup de DLL...


C'est la politique de Java:-). Une politique qui pose pas mal de
problèmes de sécurité. (Java a une possibilité de sceller un
.jar. Si un .jar a été scellé, et le class loader a chargé une
classe de ce .jar, il réfusera à charger une autre classe du
même package d'ailleurs que ce .jar. Ce qui attenue le problème
de sécurité un tout petit peu. Mais pas beaucoup.)

Le problème est simple : si tu ne difuse qu'un seul fichier (un
.exe), tu sais ce qu'il contient. Surtout, tu es 100% que
l'utilisateur ne fait pas de mise à jour partielle, et qu'il n'a
pas de melange de versions. Dès que tu as des DLL, en revanche,
tu as un problème de gestion et de vérification des versions.
(Parfois, évidemment, tu utilises des DLL exprès pour pouvoir
être indépendant des versions. Donc, par exemple, si je linke
avec un DLL d'Oracle, mon programme tourne indépendamment de si
le client a installé Oracle 8 or Oracle 9. Si j'ai linké
statiquement avec Oracle 8, en revanche, le programme ne
tournera pas sur une machine où est installé Oracle 9.)

C'est vrai, en revanche, que le C++ n'a pas un bon concepte de
modules. David Vandevoorte y travaille, mais pour l'instant,
j'ai l'impression qu'il est assez seul à y travailler (et c'est
un travail énorme). Seulement, essayer d'utiliser les DLL pour
implémenter les modules, c'est un peu faire des choses à
l'enverse : ce sont les modules qui pourraient servir au
chargement dynamique. Les DLL interviennent bien trop tard pour
pouvoir servir à la modulisation. (Pour commencer, au moins que
les choses ont beaucoup changé récemment, utiliser un DLL ne te
libère pas des en-têtes. À la fin, il n'entre en jeu que lors de
l'integration, où en fin de compte, il n'est qu'un détail
technique. Je ne vois pas en quoi il change le développement en
aval de l'integration.)

Donc, il n'y a pas un module par fichier ou par classe
obligatoirement, mais les modules on tendance à être assez
petits si on veut faire du code réutilisable, et il manque un
outil permettant de packager plusieurs modules en un seul,
afin d'éviter les problèmes d'installation.


En somme, on essaie d'utiliser les DLL comme des .class de Java,
sans avoir des fichiers .jar. Ou, exprimer différemment, on
adopte une technologie que l'expérience a montré ne marche pas.

--
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
Sylvain wrote:
Loïc Joly wrote on 20/07/2006 21:38:

Je ne suis pas sûr de comprendre. Sous Unix, et sous les
Windows classiques pré-.NET, il n'y a pas de bibliothèques
dynamiques ; malgré leur nom, une DLL se comporte comme un
fichier objet en partie pré-linké.


Par focément. On peut se lier statiquement avec une DLL, par
l'intermédiaire par exemple d'un .lib d'import, mais on peut
aussi s'y lier dynamiquement (avec des fonction LoadLibrary,
GetProcAdress...).


statique ou dynamique peut être trompeur;


Je crois en effet qu'il y a un problème de vocabulaire. Il y a
aussi plusieurs conceptes orthogonaux en jeu, qui souvent se
trouvent lié sous le même nom.

comme vous en parlez, il s'agit dans les 2 cas d'un même
fichier (.dll), distinct de l'appli et construit d'une seule
façon. il peut être "dynamiquement" remplacé (s'il est buggé
ou incomplet) du moment qu'il respecte la même table d'export.

une "librairie statique" au sens M$ (un fichier ".lib
d'implémentation") est un simple pool de .obj pré-linkés.

la différence est simplement qu'une DLL liée statiquement (par
import de son ".lib d'export") est montée d'autorité par le
code de démarrage de l'appli - qui plante donc avec une alerte
système si la DLL est absente ou invalide - alors que monter
dynamiquement une DLL permet de le faire à la demande et avec
une gestion plus souple des cas d'erreurs (absente, trop
vieille, etc).


Ce n'est donc pas une édition de liens statiques, mais une
édition de liens dynamique implicite. Il a lieu lors de
l'exécution, à chaque exécution. (Ce qui introduit encore un
variant -- non utilisé par le C++, autant que je sache. D'après
ce que j'ai entendu dire, il y a des implémentations de Java qui
met le résultat du chargement des classes dans une cache, et qui
donc font l'éditions de liens lors de l'exécution, mais
seulement lors de la première exécution, ou si les timestamps
des fichiers .class a changé.)

La question reste en ce qui concerne l'autre aspect. Est-ce
qu'il charge tout le fichier .dll, ou seulement les parties dont
il a besoin ?

Et il faut bien quelque chose du genre d'un éditeur de
liens pour les construire. (Sous Unix, c'est bien l'éditeur
de liens classique, ld, qui le fait. J'ai l'impression que
c'est aussi ce qui se passe quand je donne l'option /LD à
cl : il invoque link avec l'option /DLL.)



dans le cas d'un DLL statique, un .lib (table d'export) est
contruit avec la librairie et pris en entrée du linker de
l'appli; on peut alors avoir accès à des classes ou des
méthodes "C statiques" exportées.

pour une DLL dynamique, on se paluche le travail à la main
pour récupérer des pointeurs de fonctions - sauf erreur, on ne
peut importer de la lib la définition d'une classe;


C'est ce que je comprends aussi. De la DLL, on n'a que les
adresses des objets. Surtout, quand je compile (et donc, quand
j'ai besoin de savoir des choses comme les membres et la taille
de la classe), je n'utilise pas la DLL, mais des fichiers
d'en-tête. Exactement comme si j'allais linké statiquement.

on peut par contre mettre le code de la classe de base dans
l'appli (le dupliquer!)


Pourquoi le dupliquer ? Est-ce que le code dans la DLL n'y
pourrait pas utiliser ce qui est dans l'application ? (Il
existe, je crois, la possibilité aussi de mettre le code de la
classe de base dans son propre DLL, et donc le partager entre
l'appli et toutes les autres DLL's. D'après ce que j'ai compris,
au moins. Et évidemment, 9 fois sur 10, il n'y a pas de code
dans la classe de base, et donc, le problème ne se pose pas.)

et utiliser une factory statique pour obtenir de la lib. une
instance concrête de type inconnu (connu de la lib seule).

Ou est-ce que .NET fonctionne un peu comme Java, où les
fichiers .jar sont réelement des bibliothèques, et que les
composants en sont réelement extraits et linkés lors de
l'exécution, sans jamais avoir été linkés entre eux avant ?
(C'est un des grands défauts de Java, qui fait qu'on ne
peut pas s'en servir dans certaines applications, où la
sécurité ou la fiabilité sont des critères importants.)



non Java repose sur un class loader pour charger depuis un
.class ou un .jar le code d'une classe connue au moment où le
client a été compilé.


Il charge toujours en fait un .class, comme j'ai expliqué
ailleurs. Avec quelques bricolages pour essayer de ratrapper la
sécurité qu'il a perdu avec le chargement dynamique.

Il y a une autre différence très importante par rapport au
C++ : le compilateur sait aussi utiliser les fichiers .class
(d'un .jar ou d'un répertoire) pour obtenir les informations
qu'en C++ on trouve d'habitude dans un fichier d'en-tête. Si
donc j'écris :
import java.util.Vector ;
le compilateur cherche (selon les mêmes règles qu'utilise le JVM
lors de l'exécution) un .class pour cette classe, et en extrait
toutes les informations sur la classe.

java-beans a ajouté la réflectivité pour permettre de charger
des choses inconnues et les utiliser ensuite (de manière
tordue et dispendieuse).


La réflectivité est présente déjà dans Java ; les beans sont
plutôt une convention pour s'en servir. Et comme tu dis, le tout
est plutôt lourd.

le class loader peut être supplanté si des besoins
sécuritaires impose le chargement des classes depuis une
source sure; le chargement à l'aveugle du premier fichier
ayant le bon nom existe pareillement en C++ (sous linux comme
Wintel).


Sauf que si tu linkes statiquement, le premier fichier ayant le
bon nom est cherché dans un environement que tu contrôles.

Les DLL externes sont utilisée à l'exécution, bien
évidemment, mais aussi à la compilation, où elles ont pour
seul(*) but de permettre par de l'introspection de publier
leur contenu, remplaçant ainsi les .h.


dans le cas de .NET

Donc, il n'y a pas un module par fichier ou par classe
obligatoirement, mais les modules on tendance à être assez
petits si on veut faire du code réutilisable, et il manque
un outil permettant de packager plusieurs modules en un
seul, afin d'éviter les problèmes d'installation.


je dirais les problèmes de pollution (création d'une plétore
de fichiers sur la machine de l'utilisateur final) plus que
d'installation stricte (si on peut installer 1 DLL, on peut en
installer N);


Le problème, c'est d'assurer la cohérence et la compatibilité
(des versions, etc.). S'il n'y a qu'un seul fichier (le .exe),
la cohérence et la compatibilité sont assurées par définition.
Dès qu'il y a plus d'un, tu peux être sûr qu'un utilisateur
quelque part va réussir à avoir des versions incompatible. (Il y
a un système de gestion de versions sous Solaris, et je crois
généralement sous Unix, mais c'est vraiment un bricolage
primitif, même s'il aide.)

le découpage permet également de patcher à moindre frais de
transfert (une petite DLL de qlq centaines de Ko plutôt qu'une
assemblie de plusieurs mégas).


D'une part. De l'autre, c'est justement ce genre de patchage qui
mène à ce qu'un client ait des versions incompatbiles.

--
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
Loïc Joly wrote:

je n'essaierais jamais de passer le moindre template entre 2
codes distincts, c'est perdre son temps et finalement
inefficace (par comparaison à 2 méthodes foo(char*) et
foo(wchar_t*) plus pereines par exemple).


Déjà, là, on passe par des pointeurs, donc risque de
corruption mémoire. Rien ne t'empêche de fournir une
interface foo (std::string ) et foo(std::wstring). Il faut
juste savoir que ton client devra utiliser le même compilateur
avec les mêmes options.


Le problème, c'est que c'est une contrainte nouvelle. Plus ou
moins : on sait depuis longtemps qu'il ne faut pas jouer avec
des options du genre pack. Mais c'est une nouveauté de ne pas
pouvoir linker du code compilé en mode débug avec celui compilé
en mode optimisé (au moins dans le monde de Unix). Du coup, si
j'ai une bibliothèque tièrce sans les options de debug (en
général le cas), je ne peux pas utiliser les options debug dans
mon propre code.

--
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


1 2 3