Partage
  • Partager sur Facebook
  • Partager sur Twitter

Erreur export dll fonction avec template

Sujet résolu
    14 août 2022 à 10:29:12

    Bonjour,

    ------EDIT:

    J'ai préféré garder le ce sujet car la code utilisé est le même, toutefois, le problème de template est décris en fin de conversation

    je travail sur une librarie statique et j'ai un peu de mal avec l'utilisation du mot clé friend.  C'est a dire qu'actuellement, j'ai recréer une version minimal du projet avec la même erreur :

    Gravité	Code	Description	Projet	Fichier	Ligne	État de la suppression
    Erreur	C2248	'Engine::Game::~Game' : impossible d'accéder à protected membre déclaré(e) dans la classe 'Engine::Game'	StaticLib1	C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.32.31326\include\memory	3129	
    

    J'ai deux fichier Game.h et Engine.h

    Game.h:

    #pragma once
    namespace Engine
    {
    	//class World;
    
    	class Game
    	{
    		friend class Engine;
    	protected:
    		Game() = default;
    		virtual ~Game() = default;
    
    		bool wantsToQuit = false;
    	};
    }


    Engine.h

    #pragma once
    #include "pch.h"
    #include "Game.h"
    namespace Engine
    {
    	class Engine
    	{
    	public:
    		GAPI Engine() = default;
    		GAPI ~Engine() = default;
    
    		template<class T_GameType>
    		GAPI unsigned long long Run(int argc, char* argv[]);
    	private:
    		std::unique_ptr<Game> gameImpl = nullptr;
    	};
    
    	template<class T_GameType>
    	unsigned long long Engine::Run(int argc, char* argv[])
    	{
    		gameImpl = std::make_unique<T_GameType>();
    		return 0;
    	}
    }
    


    Et j'inclus Engine.h dans un fichier cpp pour qu'il soit pris en compte dans la création de la librairie:

    #include "pch.h"
    #include "framework.h"       //Inclus GAPI (dll import et export)
    #include "Engine.h"

    J'arrive pas à comprendre pourquoi j'obtiens toujours cette erreur et je pense que c'est a cause d'un mauvaise usage de friend, mais je n'arrive pas bien a savoir pourquoi.

    PS: Les exemples ci dessus ont été beaucoup vidés de leur contenus, mais j'obtiens toujours l'erreur, c'est pour réaliser un exemple minimal plus facile a lire


    -
    Edité par SébastienRoth 16 août 2022 à 11:28:48

    • Partager sur Facebook
    • Partager sur Twitter
      14 août 2022 à 13:10:13


      Salut,

      Ton principal problème vient du fait que ta donnée gameImpl est de type ... std::unique_ptr<Game>.

      En effet, contrairement à ce que te dit le message de Visual Studio, ce n'est pas ta classe Engine qui va se charger de la libération du pointeur sous-jacent de cette donnée.  Ce n'est même pas la classe std::unique_ptr qui va s'en charger, mais bien la fonction deallocate de la classe std::allocator qui sera appelée ... lors de l'appel automatique du destructeur de la classe std::unique_ptr.

      C'est comme si tu comptais sur Jean -- qui est un ami de Pierre -- pour convaincre Pierre de faire quelque chose qui lui est demandé par Henri, qui n'est "qu'une vague connaissance" de Jaques et qui n'est lui-même qu'une vague connaissance de Pierre.

      Comme Jean n'est même pas en contact, dans le cas présent avec Pierre, le fait qu'il soit son ami ne changera absolument rien: Pierre n'a aucune raison de faire confiance à Henri ;)

      Cependant, tu devrais te demander pourquoi tu veux placer le destructeur de la classe Game dans l'accessibilité protégée et, surtout, pourquoi tu veux le rendre virtuel.

      Car, typiquement, lorsque l'on a affaire à des hiérarchies de classes (car Game sera sans doute dérivé en plusieurs classes de jeux particuliers), il y a deux solutions:

      Soit le destructeur de la classe de base (Game) est public et virtuel, de manière à ce que le destructeur de la classe dérivée (n'importe quelle classe mise pour T_GameType) puisse être appelé en substitution de celui de la classe de base lorsque l'on ne connait une instance de la classe dérivée que comme étant "un objet du type de la classe de base".

      Soit le destructeur de la classe de base est protégé et non virtuel, de manière à ce que les destructeurs des classes dérivées puissent y faire appel mais sans que l'on puisse tenter d'en détruire les instances si on venait à les connaître comme étant "du type de la classe de base".

      Autrement dit, tu as le choix entre:

      class Base{
      public:
          Base() = default; // le constructeur pourrait être protected 
                            // sans que cela ne change quoi que ce soit
          Base(Base const &) = delete;
          Base & operator=(Base const &) = delete;
          virtual ~Base() = default;
      };
      class Derivee : public Base{
          /* ...*/
      };
      int main(){
          std::unique_ptr<Base> ptr = std::make_unique<Derivee>();
          /* ... */
         
      } // OK: std::unique_ptr a accès au destructeur de Base

      et

      class Base{
      protected:
          Base() = default;
          Base(Base const &) = delete;
          Base & operator=(Base const &) = delete;
          /* il n'y a aucune raison pour que le destructeuur
           * soit virtuel
           */
          ~Base() = default;
      };
      class Derivee : public Base{
          /* ...*/
      };
      int main(){
          Derivee obj;
          /* ... */
         
      } // OK: le destructeur de Derivee a bel et bien accès
        //     au destructeur de Base
      
      /* Par contre, ceci ne fontionnera pas, sous prétexte que 
       * le destructeur de Base est protégé dans ce contexte:
       */
      void foo(){
          std::unique_ptr<Base>=std::make_unique<Derivee>();
          /* ... */
      } // OUCH ... on essaye de détruire quelque chose qui est
        //          connu comme étant ... de type Base

      Tu auras compris que tu es, pour l'instant, dans la deuxième situation.

      Il y a donc deux solutions pour t'en sortir:

      La première (et la plus simple) est sans doute de faire passer le constructeur et le destructeur de ta classe Game dans l'accessibilité publique. 

      Si tu veux empêcher la création d'une instance de cette classe, tu peux toujours veiller à ce qu'une fonction soit déclarée comme étant virtuelle pure afin d'en faire une classe abstraite.

      La deuxième solution consisterait à faire en sorte que gameImpl ne soit non plus un std::unique_ptr<Game>, mais bien un pointeur intelligent sur le type réel de l'instance de la classe (dérivée de Game) utilisée pour le jeu.

      Par exemple, en faisant en sorte que ce soit toute ta classe Engine qui soit template au lieu de ne rendre que la fonction run template.  Cela pourrait prendre une forme proche de

      template <typename T_GameType>
      class Engine{
      public:
          size_t run(int argc, char * argv[]){
              /* gameImpl n'a même plus besoin d'être une donnée
               * membre de la classe Engine: on peut en faire une
               * donnée locale à la fonction 
               */
                T_GameType gameImpl;
                /* ... */
          }
      };

      Alors, bien sur, cela impliquerait de changer son fusil d'épaule pour ce qui est de la mise en oeuvre, car, pour instancier notre classe Engine, nous devrons soit indiquer exactement le type de Game qui sera utilisé, sous une forme proche de

      int main(){
          /* MyCoolGame hérite de Game, tu t'en sera douté  */
          Engine<MyCoolGame> engine;
          auto result = engine.run();
          /* ... */
      }

      soit à créer une classe qui dérive d'une spécialisation totale de la classe Engine sous une forme proche de

      /* Il y a toujours la classe MyCoolGame qui dérive de
       * Game  
       */
      class MyConcreteEngine : public Engine<MyCoolGame>{
      
      };
      int main(){
         MyConcreteEngine engine;
         engine.run();
         /* ... */
      }

      Evidemment, cela signifierait que tu ne peux plus t'appuyer sur le comportement de la classe Engine pour permettre le changement de jeu "à chaud"...  Mais est-ce un réel problème?

      -
      Edité par koala01 14 août 2022 à 13:26:52

      • Partager sur Facebook
      • Partager sur Twitter
      Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
        14 août 2022 à 13:17:58

        Bonjour,

        Ce qui détruit un Game, ça n'est pas Engine.
        Quand un Engine disparaît, gameImpl disparaît entrainant la disparition d'un Game.
        Donc c'est std::unique_ptr<Game>le coupable.

        Mais est-ce nécessaire d'avoir un destructeur à fois virtual et protected. Il me semble que dans ce cas, le besoin est un constructeur protected et un destructeur virtual.

        Edit: je suis un peu lent.

        -
        Edité par Dalfab 14 août 2022 à 13:22:07

        • Partager sur Facebook
        • Partager sur Twitter

        En recherche d'emploi.

          15 août 2022 à 20:33:05

          Salut, merci beaucoup pour vos réponses rapide, j'ai rapidement pu voir le problème et surtout le comprendre :) . J'avoue que je n'avais pas du tout pensé a la destruction de Game induite par unique_ptr.

          Je réouve le sujet parce que le même code est utilisé, ça évite de rouvrir un sujet avec exactement le même code:

          J'essaie actuellement de transformer ma librairie statique en librairie dynamique, j'ai les symbole d'export défini comme tel:

          #ifdef GEXPORT
          // Exports
          #ifdef _MSC_VER
          #define GAPI __declspec(dllexport)
          #else
          #define GAPI __attribute__((visibility("default")))
          #endif
          #else
          // Imports
          #ifdef _MSC_VER
          /** @brief Import/export qualifier */
          #define GAPI __declspec(dllimport)
          #else
          /** @brief Import/export qualifier */
          #define GAPI
          #endif
          #endif

          Toutefois je n'arrive pas a exporter correctement ma fonction Run, j'ai l'impression que ça vient du template. C'est a dire que si j'essaie de mettre l'implémentation dans mon .h, j'obtiens une erreur:

          Erreur	C2491	'Engine::Engine::Run' : definition of dllimport function not allowed	Project1	

          Due a la redéfinition de la fonction par mon executable.

          Mais si j'essaie de mettre l'implementation dans le .cpp, j'obtiens une erreur:

          error LNK2019: symbole externe non résolu "__declspec(dllimport) public: unsigned __int64 __cdecl Engine::Engine::Run<class my_game>(int,char * * const)" (__imp_??$Run@Vmy_game@@@Engine@0@QEAA_KHQEAPEAD@Z) référencé dans la fonction main


          Pourquoi est-ce que l'export de ma fonction ne se fait pas correctement? Je ne peux pas faire de specialisation étant donné que le type (my_game) est crée dans l'executable.

          -
          Edité par SébastienRoth 16 août 2022 à 11:19:15

          • Partager sur Facebook
          • Partager sur Twitter
            16 août 2022 à 14:58:03

            Ca n'a aucun sens d'exporter un template. Cela ne peut avoir un sens que dans le cas d'une instantiation explicite, et dans ce cas il faut mettre ton dllexport au niveau de la définition de l'instantiation explicite (qui doit se trouver dans une et une seule unité de compilation). En fait la version propre est assez complexe, donc n'exporte pas ça, c'est inliné.

            EDIT: ton 1er message est étrange, pour moi tu travailles sur une librairie partagée, et non pas statique.

            -
            Edité par SpaceIn 16 août 2022 à 15:06:11

            • Partager sur Facebook
            • Partager sur Twitter
              16 août 2022 à 15:33:11

              Je travaille effectivement sur une librairie partagé, c'est justement en tentant de passer d'une librairie statique a partagé que le problème est survenue. Toutefois, je ne comprend pas vraiment comment sortir du problème.

              Car ici, j'ai ma librairie qui définit ce template avec la fonction Run. Mais je ne peux pas de faire d'instantiation explicit étant donné que la classe utilisé dans ce template est elle créer dans l'executable. Ma librairie n'a pas connaissance de cette classe.

              • Partager sur Facebook
              • Partager sur Twitter
                16 août 2022 à 15:42:02

                SébastienRoth a écrit:

                Car ici, j'ai ma librairie qui définit ce template avec la fonction Run. Mais je ne peux pas de faire d'instantiation explicit étant donné que la classe utilisé dans ce template est elle créer dans l'executable. Ma librairie n'a pas connaissance de cette classe.

                Comment ça ta librairie n'a pas connaissance de cette classe? Le header Engine.h est fourni pas ta librairie non? Les définitions correspondantes ont été compilés dans ta librairie, non?
                • Partager sur Facebook
                • Partager sur Twitter
                  16 août 2022 à 15:45:54

                  Je me suis mal exprimé. j'ai ma librarie avec la classe Engine et la classe Game. Et dans mon executable, mon main créer un Engine  et définit une classe dérivé de Game. Ici My_Game. Et c'est cette classe la que je veux passé à Engine dans mon main.:

                  #include <Core/Engine.h> // Venant de ma librairie
                  
                  #include "my_game.h" //Venant de mon executable, dérivé de Game
                  
                  int main(int argc, char* argv[])
                  {
                  	Engine::Engine eng{};
                  	eng.Run<my_game>(argc, argv);
                  	return EXIT_SUCCESS;
                  }

                  Donc ici my_game est définit dans l'executable et non dans la librairie. 

                  -
                  Edité par SébastienRoth 16 août 2022 à 15:46:38

                  • Partager sur Facebook
                  • Partager sur Twitter
                    16 août 2022 à 15:53:41

                    Un template ne peut pas être exporté par définition, donc tu peux enlever ce GAPI dans la déclaration de Run.
                    • Partager sur Facebook
                    • Partager sur Twitter
                      16 août 2022 à 20:56:16

                      Moi, je suis encore plus "extrémiste" que @SpaceIn, exporter des classes ou des fonctions non libres ou non "manglée" à la C, ça n'a aucun sens (car l'ABI et le mangling C++ ne sont pas standardisés (à moins que cela est changé récemment avec les modules C++20 ???)).

                      Sinon, on se retrouve avec des librairies "partagées" utilisables qu'avec des exécutables compilés avec le même compilateur ou la même version de compilateur ; tu parles d'un partage !

                      On peut définir des templates dans des fichiers d'en-tête de librairies partagées mais pour simplifier l'utilisation de choses exportées par la librairie partagées, pas pour exporter/importer les templates.

                      En enlevant le "GAPI" dans la déclaration de "Run", cela devient une déclaration/définition et non plus un import/export (en plus de la déclaration(exe+dll)/définition(dll)).

                      Contrairement aux types génériques de langages .NET ou C++/CLI, les templates sont générés à la compilation (de la dll, dans votre cas) et doivent déjà connaitre la valeur de leurs méta-paramètres (full specialisation).

                      Mais pourquoi avez-vous besoin d'un template et pas juste une référence ou un pointeur intelligent sur votre "Game" ?

                      P.S.: même si c'est le même code, le problème est totalement différent donc mériterait un sujet à lui tout seul.

                      • Partager sur Facebook
                      • Partager sur Twitter
                      Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
                        16 août 2022 à 22:18:11

                        Euh alors j'avoue me sentir très bête, en fait je pense que j'étais pris dans le truc, je voulais utiliser un template mais en réaliter, vous avez raison, ce n'est clairement pas la bonne solution. Surement que je trouvait ça plus "propre" pour appeler Engine<my_game>. Mais au final j'ai passer une journée a me casser la tête pour rien.

                        Finalement, j'ai crée un unique_ptr de my_game que j'ai envoyé a Engine. Ca m'a pris 10 min a faire, et tout marchait parfaitement.... Merci beaucoup de m'avoir ramené sur terre XD

                        • Partager sur Facebook
                        • Partager sur Twitter

                        Erreur export dll fonction avec template

                        × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                        × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                        • Editeur
                        • Markdown