web-dev-qa-db-fra.com

Comment puis-je utiliser les classes Standard Library (STL) dans mon interface dll ou ABI?

Il y a eu quelques questions auparavant sur l'exportation d'une classe qui contient des classes stl par rapport à l'avertissement C4251 de Visual Studio: par ex. cette question ou cette question. J'ai déjà lu l'excellente explication sur UnknownRoad.

Désactiver aveuglément l'avertissement semble un peu dangereux, bien que cela puisse être une option. Envelopper toutes ces classes std et les exporter n'est pas non plus vraiment une option. On l'appelle après tout la Standard Template Library ... C'est-à-dire que l'on veut fournir une interface avec ces classes standard.

Comment utiliser les classes stl dans mon interface DLL? Quelles sont les pratiques courantes?

62
André

Gardez à l'esprit une chose avant de continuer à lire: Ma réponse vient du point de vue de l'écriture de code portable qui peut être utilisé dans des applications composées de modules compilés sous différents compilateurs. Cela peut inclure différentes versions ou même différents niveaux de correctifs du même compilateur.

Comment utiliser les classes stl dans mon interface DLL?

Réponse: Souvent, vous ne pouvez pas1.

Raison: La STL est une bibliothèque de code, pas une bibliothèque binaire comme une DLL. Il n'a pas un seul ABI qui est garanti d'être le même partout où vous pourriez l'utiliser. En effet, STL signifie " Bibliothèque de modèles standard ", mais un mot clé ici en plus de Standard est Template .

La norme définit les méthodes et les membres de données que chaque classe STL doit fournir et définit ce que ces méthodes doivent faire; mais pas plus. En particulier, la norme ne spécifie pas comment les rédacteurs du compilateur doivent implémenter la fonctionnalité définie par la norme. Les rédacteurs du compilateur sont libres de fournir une implémentation d'une classe STL qui ajoute des fonctions membres et des variables membres non répertoriées dans la norme, tant que les membres qui sont définis dans la norme sont toujours là et font ce que dit la norme.

Peut-être qu'un exemple est en règle. La classe basic_string Est définie dans la norme comme ayant certaines fonctions et variables membres. La définition réelle est de près de 4 pages dans la norme, mais en voici juste un extrait:

namespace std {
  template<class charT, class traits = char_traits<charT>,
    class Allocator = allocator<charT> >
  class basic_string {
[snip]
  public:
    // 21.3.3 capacity:
    size_type size() const;
    size_type length() const;
    size_type max_size() const;
    void resize(size_type n, charT c);
    void resize(size_type n);
    size_type capacity() const;
    void reserve(size_type res_arg = 0);
    void clear();
    bool empty() const;
[snip]
};

Considérez les fonctions membres size() et length(). Il n'y a rien dans la norme qui spécifie les variables membres pour conserver ces informations. En effet, aucune variable membre n'est définie, pas même pour contenir la chaîne elle-même. Alors, comment est-ce mis en œuvre?

La réponse est, de nombreuses façons différentes. Certains compilateurs peuvent utiliser une variable membre size_t Pour contenir la taille et un char* Pour contenir la chaîne. Un autre pourrait utiliser un pointeur vers un autre magasin de données qui contient ces données (cela pourrait être le cas dans une implémentation comptée par référence). En fait, différentes versions ou même niveaux de correctifs du même compilateur peuvent modifier ces détails d'implémentation. Vous ne pouvez pas compter sur eux. Ainsi, l'implémentation de MSVC 10 pourrait ressembler à ceci:

namespace std {
  template<class charT, class traits = char_traits<charT>,
    class Allocator = allocator<charT> >
  class basic_string {
[snip]
char* m_pTheString;
};

size_t basic_string::size() const { return strlen(m_pTheString;) }

... Alors que MSVC 10 avec SP1 pourrait ressembler à ceci:

namespace std {
  template<class charT, class traits = char_traits<charT>,
    class Allocator = allocator<charT> >
  class basic_string {
[snip]
vector<char> m_TheString;
};

size_t basic_string::size() const { return m_TheString.size(); }

Je ne dis pas qu'ils faites ressemblent à ceci, je dis qu'ils pourraient. Le point ici est que la mise en œuvre réelle dépend de la plate-forme, et vous n'avez vraiment aucun moyen de savoir ce que ce sera ailleurs.

Supposons maintenant que vous utilisez MSVC10 pour écrire un DLL qui exporte cette classe:

class MyGizmo
{
public:
  std::string name_;
};

Qu'est-ce que la sizeof(MyGizmo)?

En supposant que mes implémentations proposées ci-dessus, sous MSVC10, ce sera sizeof(char*), mais sous SP1, ce sera sizeof(vector<char>). Si vous écrivez une application dans VC10 SP1 qui utilise la DLL, la taille de l'objet sera différente de ce qu'elle est réellement. L'interface binaire est modifiée.


Pour un autre traitement de cela, veuillez consulter Normes de codage C++ (Amazon link ) problème # 63.


1: " Souvent, vous ne pouvez pas " Vous pouvez réellement exporter des composants de bibliothèque standard ou tout autre composant de bibliothèque de code (comme Boost) avec une bonne fiabilité lorsque vous avez un contrôle complet sur les chaînes d'outils et les bibliothèques.

Le problème fondamental est qu'avec les bibliothèques de code source, les tailles et définitions des choses peuvent être différentes entre différents compilateurs et différentes versions de la bibliothèque. Si vous travaillez dans un environnement où vous contrôlez ces deux choses partout où votre code est utilisé, vous n'aurez probablement pas de problème. Par exemple, dans une société de négoce où tous les systèmes sont écrits en interne et utilisés uniquement en interne, il peut être possible de le faire.

88
John Dibling