11 votes

Exportation de DLL membres statiques d'une classe de base modèle

Dans une DLL, j'ai une classe non modèle exportée avec une classe de base modèle. Cette classe de base modèle a une variable membre statique. J'utilise le membre de base statique dans un exécutable qui se lie à la DLL avec la classe non modèle exportée.

Dans de nombreux scénarios, j'obtiens des symboles externes non résolus ou des plaintes sur une liaison incohérente. J'ai trouvé un scénario qui fonctionne, mais il semble être bricolé, donc je me demande s'il y a une meilleure façon de faire et si cette meilleure façon peut également pointer vers des lacunes dans le compilateur/lieur C++ de VS2010 SP1.

Il s'agit du scénario minimal de la DLL que j'ai pu distiller - je ne pense pas pouvoir enlever quoi que ce soit ici sans casser le scénario.

// Fichier en-tête
template
class _MYDLL_EXPORTS TBaseClass
  {
  public:
    static const double g_initial_value;
  };

class _MYDLL_EXPORTS MyClass : public TBaseClass
  {    
  };

// Bricolage : utilisez ce code uniquement lors de la construction de la DLL, pas lors de son inclusion
// depuis le client de la DLL
#ifdef _MYDLL
  template
  const double TBaseClass::g_initial_value = 1e-5;
#endif

// Fichier CPP
#include "header.h"
// Instanciation explicite du modèle pour le bon paramètre.
template class TBaseClass;

Ensuite, l'utilisateur de la DLL

#include   
#include 
int main(void) {
 MyClass c;
 std::cout << c.g_initial_value;
return 0;
}

6voto

Alan Baljeu Points 640

En C++, généralement, lorsqu'une classe simple a un membre statique, il doit être déclaré dans l'en-tête, mais instancié dans un fichier source. Faire autrement entraînerait la création de trop nombreuses instances du membre statique de la classe.

Les modèles sont un peu similaires, sauf que le compilateur a quelques astuces pour les modèles qu'il n'a pas pour les non-modèles. En particulier, il élimine automatiquement les instances dupliquées d'une instantiation de modèle lors de la phase de liaison d'une construction.

Ceci est à l'origine de votre problème : Le contenu à l'intérieur de la partie _MYDLL est automatiquement instancié par chaque fichier source qui inclut ce modèle et qui crée également des objets TBaseClass. Ensuite, le lien élimine automatiquement les doublons.

Le problème est que vous avez deux liens : le lien DLL et le lien client. Les deux créent des instanciations de TBaseClass, et les deux créent ces objets g_initial_value.

Pour résoudre cela : Déplacez le contenu de la condition _MYDLL dans le fichier CPP, afin que le client ne reçoive pas d'instructions pour construire l'instance lui-même.

4voto

Miguel Points 15459

Le fait que vous utilisiez votre classe de modèle à la fois depuis le DLL et l'EXE rend les choses plus confuses, mais cela peut quand même fonctionner.

Tout d'abord, vous devriez implémenter votre classe de base de modèle entièrement dans le fichier d'en-tête. Si vous ne savez pas pourquoi, assurez-vous de lire la réponse acceptée à cette question.

Maintenant, oublions les modèles et les DLL, et considérons un cas beaucoup plus simple. Disons que vous avez la classe C, avec un membre statique. Vous coderiez normalement cette classe de cette manière :

// fichier C.h
class C {
public:
    static const double g_initial_value;
};

// fichier C.cpp
const double C::g_initial_value = 1e-5;

Rien d'étrange ou de compliqué ici. Maintenant, imaginez ce qui se passerait si vous déplacez la déclaration statique dans le fichier d'en-tête. S'il n'y a qu'un seul fichier source qui inclut l'en-tête, alors tout fonctionnera très bien. Mais si deux fichiers source ou plus inclus cet en-tête, alors ce membre statique sera défini plusieurs fois, et le lien n'aimera pas cela.

Le même concept s'applique à une classe de modèle. Votre astuce #ifdef _MYDLL fonctionne uniquement parce que depuis le DLL, vous incluez ce fichier d'en-tête une seule fois. Mais dès que vous incluez ce fichier depuis un autre fichier source, vous commencerez à obtenir des erreurs de liaison dans le DLL! Donc je suis totalement d'accord avec vous, ce n'est pas une bonne solution.

Je pense qu'une chose qui complique votre configuration est que vous autorisez à la fois le DLL et l'EXE à instancier cette classe de base de modèle. Je pense que vous auriez une solution beaucoup plus propre si vous trouviez un "propriétaire" pour chaque instanciation de la classe de modèle. Suivant votre exemple de code, remplaçons MyClass par MyDLLClass et MyEXEClass. Vous pourriez alors faire en sorte que cela fonctionne comme ceci :

// dll.h
template
class _MYDLL_EXPORTS TBaseClass
  {
  public:
    static const double g_initial_value;
  };

class _MYDLL_EXPORTS MyDLLClass : public TBaseClass
  {    
  };

// dll.cpp
#include "dll.h"

// ce fichier "possède" MyDLLClass donc le statique est défini ici
template<> const double TBaseClass::g_initial_value = 1e-5;

// exe.h
#include "dll.h"

class MyEXEClass : public TBaseClass
  {    
  };

// exe.cpp
#include "exe.h"
#include 

// ce fichier "possède" MyEXEClass donc le statique est défini ici
template<> const double TBaseClass::g_initial_value = 1e-5;

int main(int argc, char* argv[])
{
    MyDLLClass dll;
    MyEXEClass exe;

    std::cout << dll.g_initial_value;
    std::cout << exe.g_initial_value;
}

J'espère que cela a du sens.

1voto

langdead Points 11

En fait, la classe exportée de la classe de base est également exportée si la classe de base est une classe modèle, mais ce n'est pas vrai dans l'autre sens. Veuillez vous référer à http://www.codesynthesis.com/~boris/blog/2010/01/18/dll-export-cxx-templates/

Pour votre question spécifique, je vous suggère de définir une méthode statique dans le modèle de base qui renvoie une variable (pointeur ?) d'intérêt. Alors, une seule définition se produira à travers plusieurs dlls ou exe qui dépendent de votre bibliothèque.

0voto

Xentrax Points 191

Alors que je suggérerais d'utiliser votre approche actuelle, il est en fait possible d'éviter #ifdef en utilisant une ancienne syntaxe pour exporter des modèles à partir d'un DLL. Tout cela se trouve dans le fichier d'en-tête du DLL :

#pragma once

#ifdef _MYDLL
#define _MYDLL_EXPORTS __declspec(dllexport)
#else
#define _MYDLL_EXPORTS __declspec(dllimport)
#endif

template 
class _MYDLL_EXPORTS TBaseClass // _MYDLL_EXPORTS n'est pas nécessaire ici
{ 
public: 
    static double g_initial_value; 
}; 

template 
double TBaseClass::g_initial_value = 1e-5; 

class MyClass;

template class _MYDLL_EXPORTS TBaseClass;

class _MYDLL_EXPORTS MyClass : public TBaseClass 
{     
}; 

À l'exécution, l'adresse de g_initial_value dans le code client se situe dans l'espace d'adressage du DLL, il semble donc fonctionner correctement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X