web-dev-qa-db-fra.com

Exportation de classes contenant des objets std :: (vecteur, carte, etc.) à partir d'une DLL

J'essaie d'exporter des classes à partir d'un DLL qui contient des objets tels que std :: vectors et std :: strings - la classe entière est déclarée comme exportation dll via:

    class DLL_EXPORT FontManager
{

Le problème est que pour les membres des types complexes, j'obtiens cet avertissement:

avertissement C4251: 'FontManager :: m__fonts': la classe 'std :: map <_Kty, _Ty>' doit avoir une interface dll pour être utilisée par les clients de la classe 'FontManager' avec [_Kty = std :: string, _Ty = tFontInfoRef ]

Je suis en mesure de supprimer certains des avertissements en leur soumettant la déclaration de classe suivante, même si je ne modifie pas le type des variables membres elles-mêmes:

template class DLL_EXPORT std::allocator<tCharGlyphProviderRef>;
template class DLL_EXPORT std::vector<tCharGlyphProviderRef,std::allocator<tCharGlyphProviderRef> >;
std::vector<tCharGlyphProviderRef> m_glyphProviders;

On dirait que la déclaration directe "injecte" le DLL_EXPORT pour quand le membre est compilé mais est-ce sûr? Cela change-t-il vraiment quelque chose lorsque le client compile cet en-tête et utilise le conteneur std de son côté? Va-t-il faire toutes les utilisations futures d'un tel conteneur DLL_EXPORT (et éventuellement pas en ligne?)? Et cela résout-il vraiment le problème que l'avertissement tente d'avertir?

Cet avertissement devrait-il m'inquiéter ou serait-il préférable de le désactiver dans le cadre de ces constructions? Les clients et la DLL seront toujours construits en utilisant le même ensemble de bibliothèques et de compilateurs et ce sont des classes d'en-tête uniquement ...

J'utilise Visual Studio 2003 avec la bibliothèque STD standard.

---- Mise à jour ----

Je voudrais vous cibler davantage si je vois que les réponses sont générales et ici nous parlons de conteneurs et de types std (tels que std :: string) - peut-être que la question est vraiment:

Pouvons-nous désactiver l'avertissement pour les conteneurs et types standard disponibles pour le client et la DLL via les mêmes en-têtes de bibliothèque et les traiter exactement comme nous traiterions un int ou tout autre type intégré? (Cela semble fonctionner correctement de mon côté.) Dans l'affirmative, quelles devraient être les conditions dans lesquelles nous pouvons le faire?

Ou peut-être que l'utilisation de tels conteneurs devrait être interdite ou au moins être extrêmement prudente pour s'assurer qu'aucun opérateur d'affectation, constructeur de copie, etc. ne sera intégré dans le client dll?

En général, j'aimerais savoir si vous pensez que la conception d'une interface dll ayant de tels objets (et par exemple les utiliser pour renvoyer des trucs au client en tant que types de valeur de retour) est une bonne idée ou non et pourquoi - j'aimerais avoir une interface "de haut niveau" pour cette fonctionnalité ... peut-être que la meilleure solution est ce que Neil Butterworth a suggéré - créer une bibliothèque statique?

58
RnR

Lorsque vous touchez un membre de votre classe à partir du client, vous devez fournir une interface DLL. Une interface DLL signifie que le compilateur crée la fonction dans le DLL lui-même et le rend importable.

Étant donné que le compilateur ne sait pas quelles méthodes sont utilisées par les clients d'une classe DLL_EXPORTED, il doit faire en sorte que toutes les méthodes soient exportées par DLL. Il doit imposer que tous les membres auxquels les clients peuvent accéder doivent également exporter leurs fonctions. Cela se produit lorsque le compilateur vous avertit des méthodes non exportées et que l'éditeur de liens du client envoie des erreurs.

Tous les membres ne doivent pas être marqués avec dll-export, par ex. membres privés non touchables par les clients. Ici, vous pouvez ignorer/désactiver les avertissements (méfiez-vous des dtor/ctors générés par le compilateur).

Sinon, les membres doivent exporter leurs méthodes. Les déclarer en avant avec DLL_EXPORT n'exporte pas les méthodes de ces classes. Vous devez marquer les classes correspondantes dans leur unité de compilation comme DLL_EXPORT.

À quoi cela se résume-t-il ... (pour les membres non exportables en DLL)

  1. Si vous avez des membres qui ne sont pas/ne peuvent pas être utilisés par les clients, désactivez l'avertissement.

  2. Si vous avez des membres qui doivent être utilisés par les clients, créez un wrapper dll-export ou créez des méthodes d'indirection.

  3. Pour réduire le nombre de membres visibles de l'extérieur, utilisez des approches telles que idiome PIMPL .


template class DLL_EXPORT std::allocator<tCharGlyphProviderRef>;

Cela crée une instanciation de la spécialisation de modèle dans l'unité de compilation actuelle. Cela crée donc les méthodes de std :: allocator dans la dll et exporte les méthodes correspondantes. Cela ne fonctionne pas pour les classes concrètes car il ne s'agit que d'une instanciation de classes modèles.

53
Christopher

Cet avertissement vous indique que les utilisateurs de votre DLL n'ont pas accès à vos variables membres de conteneur à travers la frontière DLL. Les exporter explicitement les rend disponibles, mais Est-ce que c'est une bonne idée?

En général, j'éviterais d'exporter des conteneurs std depuis votre DLL. Si vous pouvez absolument garantir que votre DLL sera utilisé avec le même runtime et la même version du compilateur que vous seriez en sécurité. Vous devez vous assurer que la mémoire allouée dans votre DLL est désalloués en utilisant le même gestionnaire de mémoire. Sinon, au mieux, vous affirmerez au moment de l'exécution.

Donc, n'exposez pas les conteneurs directement à travers les limites DLL. Si vous avez besoin d'exposer des éléments de conteneur, faites-le via des méthodes d'accesseur. Dans le cas que vous avez fourni, séparez l'interface de l'implémentation et exposez le interface au niveau DLL. Votre utilisation des conteneurs std est un détail d'implémentation auquel le client de votre DLL ne devrait pas avoir besoin d'accéder).

Sinon, faites ce que Neil suggère et créez une bibliothèque statique au lieu d'une DLL. Vous perdez la possibilité de charger la bibliothèque au moment de l'exécution, et les consommateurs de votre bibliothèque doivent se reconnecter chaque fois que vous modifiez votre bibliothèque. Si ce sont des compromis avec lesquels vous pouvez vivre, une bibliothèque statique vous permettrait au moins de résoudre ce problème. Je soutiendrai toujours que vous exposez inutilement les détails de l'implémentation, mais cela pourrait avoir du sens pour votre bibliothèque particulière.

17
Aaron Saarela

Il y a d'autres problèmes.

Certains conteneurs STL sont "sûrs" pour l'exportation (comme le vecteur), et certains ne le sont pas (par exemple la carte).

La carte par exemple n'est pas sûre car elle (dans la distribution MS STL de toute façon) contient un membre statique appelé _Nil, dont la valeur est comparée dans l'itération pour tester la fin. Chaque module compilé avec STL a une valeur différente pour _Nil, et donc une carte créée dans un module ne sera pas itérable à partir d'un autre module (elle ne détecte jamais la fin et explose).

Cela s'appliquerait même si vous créez un lien statique vers une bibliothèque, car vous ne pouvez jamais garantir la valeur de _Nil (elle n'est pas initialisée).

Je crois que STLPort ne fait pas cela.

7
Adrien

La meilleure façon que j'ai trouvée pour gérer ce scénario est:

créez votre bibliothèque, en la nommant avec le compilateur et les versions stl incluses dans le nom de la bibliothèque, exactement comme le font les bibliothèques boost.

exemples:

- FontManager-msvc10-mt.dll pour la version dll, spécifique au compilateur MSVC10, avec la stl par défaut.

- FontManager-msvc10_stlport-mt.dll pour la version dll, spécifique au compilateur MSVC10, avec le port stl.

- FontManager-msvc9-mt.dll pour la version dll, spécifique au compilateur MSVC 2008, avec la stl par défaut

- libFontManager-msvc10-mt.lib pour la version lib statique, spécifique au compilateur MSVC10, avec la stl par défaut.

en suivant ce modèle, vous éviterez les problèmes liés aux différentes implémentations stl. rappelez-vous, l'implémentation stl dans vc2008 diffère de l'implémentation stl dans vc2010.

Voir votre exemple en utilisant la bibliothèque boost :: config:

#include <boost/config.hpp>

#ifdef BOOST_MSVC
#  pragma warning( Push )
#  pragma warning( disable: 4251 )
#endif

class DLL_EXPORT FontManager
{
public:
   std::map<int, std::string> int2string_map;
}

#ifdef BOOST_MSVC
#  pragma warning( pop )
#endif

Une alternative que peu de gens semblent envisager est de ne pas utiliser du tout un DLL mais de se lier statiquement à une bibliothèque .LIB statique. Si vous faites cela, tous les problèmes d'exportation/importation disparaissent (bien que vous ayez toujours des problèmes de manipulation de nom si vous utilisez différents compilateurs.) Vous perdez bien sûr les fonctionnalités de l'architecture DLL, telles que le chargement des fonctions au moment de l'exécution, mais cela peut être un petit prix à payer dans de nombreux cas.

5
anon

Trouvé cet article . En bref, Aaron a la "vraie" réponse ci-dessus; N'exposez pas les conteneurs standard au-delà des limites de la bibliothèque.

4
Chris Huang-Leaver

Bien que ce fil soit assez ancien, j'ai récemment trouvé un problème, ce qui m'a fait penser à nouveau à la présence de modèles dans mes classes exportées:

J'ai écrit une classe qui avait un membre privé de type std :: map. Tout fonctionnait assez bien jusqu'à ce qu'il soit compilé en mode de publication, même lorsqu'il est utilisé dans un système de génération, ce qui garantit que tous les paramètres du compilateur sont les mêmes pour toutes les cibles. La carte était complètement cachée et rien n'était directement exposé aux clients.

En conséquence, le code venait juste de planter en mode release. Je gues, car différentes instances binaires std :: map ont été créées pour l'implémentation et le code client.

Je suppose que la norme C++ ne dit rien sur la façon dont cela doit être géré pour les classes exportées car cela est à peu près spécifique au compilateur. Je suppose donc que la plus grande règle de portabilité consiste à simplement exposer les interfaces et à utiliser l'idiome PIMPL autant que possible.

Merci pour tout éclaircissement

3
TheBigW

Exportation de classes contenant des objets std :: (vecteur, carte, etc.) à partir d'une DLL

Voir également l'article KB 168958 de Microsoft Comment exporter une instanciation d'une classe STL (Standard Template Library) et une classe qui contient un membre de données qui est un objet STL . De l'article:

Pour exporter une classe STL

  1. Dans les deux DLL et le fichier .exe, liez avec la même version DLL de l'exécution C. Soit liez les deux avec Msvcrt.lib (version de version ) ou liez les deux avec Msvcrtd.lib (version de débogage).
  2. Dans la DLL, fournissez le spécificateur __declspec dans la déclaration d'instanciation du modèle pour exporter l'instanciation de la classe STL à partir de la DLL.
  3. Dans le fichier .exe, fournissez les spécificateurs extern et __declspec dans la déclaration d'instanciation du modèle pour importer la classe à partir de la DLL. Il en résulte un avertissement C4231 "extension non standard utilisée: 'extern' avant l'instanciation explicite du modèle." Vous pouvez ignorer cet avertissement.

Et:

Pour exporter une classe contenant un membre de données qui est un objet STL

  1. Dans les deux DLL et le fichier .exe, liez avec la même version DLL de l'exécution C. Soit liez les deux avec Msvcrt.lib (version de version ) ou liez les deux avec Msvcrtd.lib (version de débogage).
  2. Dans la DLL, fournissez le spécificateur __declspec dans la déclaration d'instanciation du modèle pour exporter l'instanciation de la classe STL à partir de la DLL.

    REMARQUE: vous ne pouvez pas ignorer l'étape 2. Vous devez exporter l'instanciation de la classe STL que vous utilisez pour créer le membre de données.
  3. Dans la DLL, fournissez le spécificateur __declspec dans la déclaration de la classe pour exporter la classe à partir de la DLL.
  4. Dans le fichier .exe, fournissez le spécificateur __declspec dans la déclaration de la classe pour importer la classe à partir de la DLL. Si la classe que vous exportez a une ou plusieurs classes de base, vous devez également exporter les classes de base.

    Si la classe que vous exportez contient des membres de données de type classe, vous devez également exporter les classes des membres de données.
2
jww

Dans de tels cas, considérez les utilisations de l'idiome pimpl. Cachez tous les types complexes derrière un seul vide *. Le compilateur ne parvient généralement pas à remarquer que vos membres sont privés et toutes les méthodes incluses dans la DLL.

1
MSalters

aucune des solutions de contournement ci-dessus n'est acceptable avec MSVC en raison des membres de données statiques à l'intérieur des classes de modèle comme les conteneurs stl

chaque module (dll/exe) obtient sa propre copie de chaque définition statique ... wow! cela conduira à des choses terribles si vous "exportez" ces données (comme "indiqué" ci-dessus) ... alors n'essayez pas cela à la maison

voir http://support.Microsoft.com/kb/172396/en-us

0
kcris

La meilleure approche à utiliser dans de tels scénarios consiste à utiliser le modèle de conception PIMPL.

0
munsingh

Si vous utilisez un DLL faites l'initialisation de tous les objets à l'événement "DLL PROCESS ATTACH" et exportez un pointeur vers ses classes/objets.
Vous pouvez fournir des fonctions spécifiques pour créer et détruire des objets et des fonctions pour obtenir le pointeur des objets créés, afin que vous puissiez encapsuler ces appels dans une classe d'accès wrapper dans le fichier include.

0
lsalamon