[C/C++] Singletons et plugins

Le côté programmation du développement d'un jeu vidéo.

Singletons et plugins

Messagepar Rewpparo » 16 Oct 2008, 21:57

Bonjour a tous

Je suis en train de me prendre la tête pour arriver a faire un système de singleton qui puisse se transmettre a un plugin.
Comme certains le savent, le plus gros problème des singletons est qu'ils reposent sur des variables statiques, qui ont la particularité de ne pas se transmettre a un plugin. Si je créé des singletons dans un programme, puis que je charge un plugin, le plugin ne verra pas les singletons et une 2eme instance du singleton pourra être créée. Mon idée était donc de faire en sorte que les singletons s'enregistrent lors de leur création a un autre singleton, et je n'aurais qu'a envoyer cette base de donnée au plugin pour que les singletons soient synchronisés et que tout marche comme si le problème n'existait pas.

Mais j'ai du mal a mettre ça en place, et quelques conseils avisés ne seraient pas de refus.

J'ai d'abord fait une classe Singleton<> normale, templatée. Mais pour pouvoir stocker les instances, j'ai du faire dériver cette classe templatée d'une classe non templatée, SingletonBase, car je ne peux pas stocker des listes de classes avec des arguments de template différents.
Je récupère donc une liste de SingletonBase, et il me faut concevoir une méthode pour que cette classe arrive a mettre a jour la variable globale de la classe dérivée, Singleton<>. La variable doit être dans la classe templatée pour assurer l'unicité pour chaque type.

Ça marche si la méthode est dans la classe templatée Singleton<>, la même qui stocke la variable. La mise a jour se fait parfaitement. Le problème c'est que le plugin ne connait pas le type sous lequel le Singleton<> est instancié. Je ne peux pas lui dire, car une fonction templatée n'est pas exécutable dans un plugin depuis l'extérieur. Et je ne veux pas limiter mon système a une liste définie de singletons.

Utiliser une fonction virtuelle ne fonctionne pas, puisque le code change alors la variable dans le programme et non dans le plugin.


Si je n'y arrive pas, j'ai d'autres solutions, mais il faudrait parcourir un vector pour chaque demande de singleton, ce que j'aimerais éviter si possible.
Si quelqu'un avait une autre idée d'approche pour arriver a résoudre ce problème, je lui en serais très reconnaissant.
Avatar de l’utilisateur
Rewpparo
Hello World, I'm new !
 
Messages: 41
Inscription: 30 Nov 2007, 14:04
Localisation: La rochelle

Messagepar NicoL3AS » 18 Oct 2008, 16:43

Je ne suis pas un habitué des plugins, mais je suppose que le code au sein de ceux-ci peuvent accéder aux méthodes publiques des objets du code principal, non ?
Dans ce cas, je ne comprends pas ton problème. Le constructeur doit être privé et il faut faire une fonction createObject() publique qui crée l'objet statiquement s'il n'existe pas déjà, et dans tout les cas renvoie un pointeur sur cet objet statique.
Je ne vois pas quel est ton problème, peux-tu montrer ton code ou un diagramme de tes objets ?
La Destinée du Feu
Action-RPG 2D gratuit
Avatar de l’utilisateur
NicoL3AS
Hello World, I'm new !
 
Messages: 490
Inscription: 12 Aoû 2005, 17:46

Messagepar Rewpparo » 18 Oct 2008, 17:13

Merci de ta réponse
Les objets peuvent en effet accéder au méthodes publiques du code principal, mais le problème c'est que les variables statiques ne sont pas partagées.
Mettons que j'ai cette classe :
template <typename T> class Singleton
{
private:
static T* mInstance;
public:
Singleton() { assert(!mInstance); mInstance=(T*)this;}
static T* Get() {return mInstance;}
}
la classe singleton classique, réduite a son plus simple appareil. Je fais une classe Manager qui hérite de Singleton<Manager>.

Dans le code principal je fais :
new Manager
mInstance est alors initialisée correctement, Get me retournera l'instance, tout marche bien.

Si ensuite je charge un plugin, et que dans le code de ce plugin j'ai un appel a Manager::Get(), il retournera 0, car le plugin ne connait pas le mInstance que j'ai initialisé dans le programme principal. Il a une autre variable, qui s'appelle mInstance aussi, mais sont la valeur est totalement déconnectée de celle du programme principal. Je peux faire new Manager() dans le plugin, aucune erreur ne sera renvoyée. Mon plugin travaillera sur son instance, et le programme principal sur la sienne, ce qui va a l'encontre du but des singletons.
Mon but est donc de trouver un moyen de synchroniser ces deux valeurs de mInstance, et ce sans que le plugin ne connaisse le type selon lequel est instancié le singleton.

Désolé de ne pas poster de code qui de toutes façons illustrerait moins bien mon propos, mais je développe sous Linux, donc le code ne compilerait pas sous Windows que tu utilises probablement
Avatar de l’utilisateur
Rewpparo
Hello World, I'm new !
 
Messages: 41
Inscription: 30 Nov 2007, 14:04
Localisation: La rochelle

Messagepar NicoL3AS » 19 Oct 2008, 18:15

J'ai fait quelques tests, et déjà il y a quelque chose qui cloche :
Comment arrives-tu à compiler ?
Le propre d'un attribut statique, c'est d'être reservé dès le début du programme, et le propre d'une classe template c'est de ne pas être réservé dès le début du programme (enfin, je crois).
Donc un attribut statique dans une fonction template, c'est paradoxal non ?

J'ai strictement repris ton code et j'ai fait cela :
Code: Tout sélectionner
#include <iostream>

template <typename T> class Singleton
{
   private:
      static T* mInstance;
   public:
      Singleton()
      {
         assert(!mInstance);
         mInstance=(T*)this;
      }
      static T* Get() {return mInstance;}
};

class Manager : public Singleton<Manager>
{
   private:
      int premierAttribut;
   public:
      Manager() { premierAttribut = 3; }
      int getPremier() { return premierAttribut; }
};

int main()
{
   Manager* test = new Manager;
   std::cout << test->getPremier() << std::endl;
   return 0;
}


Le compilateur me sort cela :
Code: Tout sélectionner
g++ -O3 -c Main.cpp
g++ -o Main Main.o
Main.o(.text+0x5c):Main.cpp: undefined reference to `Singleton<Manager>::mInstan
ce'
Main.o(.text+0x79):Main.cpp: undefined reference to `Singleton<Manager>::mInstan
ce'
..\MinGWsa\bin\make: *** [Main] Error 1


Et c'est tout à fait logique !

Rewpparo a écrit:Dans le code principal je fais :
new Manager
mInstance est alors initialisée correctement, Get me retournera l'instance, tout marche bien.

Comment as-tu fait ?! Cela vient-il d'une différence entre Linux et Windows (car en effet je travaille sous Windows) ? Ou bien ais-je loupé quelque chose ?

Car en toute logique, ton problème n'a pas lieu d'être, une variable statique dans le code principale sera récupérable par le Get() que ce soit dans ce même code ou dans celui de ton .so
La Destinée du Feu
Action-RPG 2D gratuit
Avatar de l’utilisateur
NicoL3AS
Hello World, I'm new !
 
Messages: 490
Inscription: 12 Aoû 2005, 17:46

Messagepar Rewpparo » 20 Oct 2008, 02:46

La variable statique doit etre décrite explicitement avec cette ligne:
template<> Manager* Singleton<Manager>::mInstance=0;
Quelque part après ta déclaration de manager.

Un template existe bien au début du programme, ils sont instanciés a la compilation, et deviennent alors des classes tout ce qu'il y a de plus normal. Il n'y a aucune raison qu'on ne puisse pas faire avec un template ce qu'on peut faire avec une classe normale.
En l'occurence, pour les variables statiques ca a le gros avantage d'en créer une a chaque instanciation de template, c'est a dire une par type, ce qui est précisément ce qu'on cherche avec un singleton. les Singleton<Manager1> partageront la meme instance, qui sera différente des Singleton<Manager2>. C'est pour ca que les templates sont si pratiques pour les singletons.


Sinon le code correspond a ce que j'ai fait. Si tu veux vraiment rétablir les conditions de l'erreur, il faut que tu compiles le manager et le singleton dans un dll (y compris la ligne que je t'ai donné), que tu fasses une autre dll avec juste une fonction globale qui fait faire quelque chose au singleton. Fais new manager dans le programme principal, ensuite charge le petit dll comme plugin, et executes la fonction. Ca va planter (utilisation d'un pointeur nul).

J'ai trouvé un code qui devrait marcher sous visual studio (sans garantie vu que je n'ai pas pu l'essayer)

Engine.dll
*Engine.h
Code: Tout sélectionner
template <typename T> class Singleton
{
   private:
      static T* mInstance;
   public:
      Singleton()
      {
         assert(!mInstance);
         mInstance=(T*)this;
      }
      static T* Get() {return mInstance;}
};

class Manager : public Singleton<Manager>
{
   private:
      int premierAttribut;
   public:
      Manager() { premierAttribut = 3; }
      int getPremier() { return premierAttribut; }
};

*Engine.cpp
Code: Tout sélectionner
template<> Manager* Singleton<Manager>::mInstance=0;


Plugin.dll
*Plugin.cpp
Code: Tout sélectionner
#include "Engine.h"
extern "C" void execute()
{
if(!Manager::Get()) std::cout << "Le plugin ne connait pas le singleton" << std::endl;
}


programme
*programme.cpp
Code: Tout sélectionner
int main()
{
new manager;
if(Manager::Get()) std::cout << "Le programme connait le singleton" << std::endl;
//On charge le plugin
HINSTANCE hDLL= LoadLibrary("Plugin.dll");
if (hDLL == NULL) return; //erreur
//On recupere un pointeur vers la fonction execute
LPFNDLLFUNC1 lpfnDllFunc1 = (LPFNDLLFUNC1)GetProcAddress(hDLL,"execute"); ////C'est quoi ces types de fou ?? pour info c'est un void* sous nux
if (!lpfnDllFunc1) return; //erreur
//et on execute   void execute()
lpfnDllFunc1();
FreeLibrary(hDLL);
}


Il y a surement du taf pour arriver a faire compiler ca, il manque notament les headers que je n'ai pas trouvé (windows.h ?), mais dans les grandes lignes ca devrait etre pas mal. Tu peux peut etre t'epargner de faire le dll en incluant ma ligne magique dans le plugin ET le programme. Ne lie pas le plugin au programme, juste engine.dll. Plugin.dll sera chargé a l'exection.
Encore une fois, merci de ton temps
Avatar de l’utilisateur
Rewpparo
Hello World, I'm new !
 
Messages: 41
Inscription: 30 Nov 2007, 14:04
Localisation: La rochelle

Messagepar Ced666 » 20 Oct 2008, 09:03

Hello,

Je ne connais pas le comportement sous Linux (jamais programmé sous Linux) mais tu vas avoir un petit problème avec ton code sous Windows: tu auras bien accès à ton singleton dans ton programme et dans ta dll mais ce sera deux singleton différents (un pour l'exe, un pour la dll). Enfin, je n'ai pas compris ce que tu voulais faire exactement donc j'ai un peu de mal à t'aider d'une manière plus efficace.

Sinon, pour ton commentaire dans le code ("C'est quoi ces types de fou ?? pour info c'est un void* sous nux"): le 'LPFNDLLFUNC1' est en fait définit par toi (c'est un typedef) mais dans ce cas bien précis, il est définit comme void* parce que ta fonction ne prend pas d'argument et ne retourne rien. Pour des fonctions plus complexes, tu dois adapter ton typedef de façon à refléter le prototype de ta fonction.
Ced666
Hello World, I'm new !
 
Messages: 315
Inscription: 10 Avr 2005, 09:43

Messagepar Kremtak » 20 Oct 2008, 11:02

Pour contourner le problème, peut-être que tu pourrais utiliser une variable globale dans engine.h qui pointe sur le Manager. Et depuis le plugin, appeler non pas Manager::Get() mais directement cette globale.
But there's no sense crying over every mistake
You just keep trying until you run out of cake
Avatar de l’utilisateur
Kremtak
Hello World, I'm new !
 
Messages: 167
Inscription: 12 Déc 2007, 23:27
Localisation: Aubagne

Messagepar Mokona » 20 Oct 2008, 14:43

mais tu vas avoir un petit problème avec ton code sous Windows: tu auras bien accès à ton singleton dans ton programme et dans ta dll mais ce sera deux singleton différents (un pour l'exe, un pour la dll).


C'est exactement le sujet de la discussion et le problème avancé.
Mokona
Hello World, I'm new !
 
Messages: 1686
Inscription: 13 Mar 2005, 13:00

Messagepar Rewpparo » 20 Oct 2008, 18:12

Mokona a écrit:C'est exactement le sujet de la discussion et le problème avancé.

En effet, je cherche justement un moyen de contourner le probleme que tu décris en faisant en sorte que les deux variables statiques pointent sur la meme instance.

Kremtak a écrit:Pour contourner le problème, peut-être que tu pourrais utiliser une variable globale dans engine.h qui pointe sur le Manager. Et depuis le plugin, appeler non pas Manager::Get() mais directement cette globale.

Le probleme vient justement du fait que les globales du plugin et du programme sont désynchronisés. Get ne fait rien d'autre qu'appeller la globale. Si je l'appelle manuellement, j'aurais le meme résultat. Si j'ai bien compris ce que tu as dit.

Ced666 a écrit:Hello,
Sinon, pour ton commentaire dans le code ("C'est quoi ces types de fou ?? pour info c'est un void* sous nux"): le 'LPFNDLLFUNC1' est en fait définit par toi (c'est un typedef) mais dans ce cas bien précis, il est définit comme void* parce que ta fonction ne prend pas d'argument et ne retourne rien. Pour des fonctions plus complexes, tu dois adapter ton typedef de façon à refléter le prototype de ta fonction.

Merci pour la précision. Il faudra bien que j'y touche un jour, je ne sais que dans les grandes lignes comment ca marche, j'ai pas beaucoup pratiqué.


Sinon un exemple du genre de semi solution que j'ai pu trouver. Je peux ajouter une fonction ResetInstance() au singleton de cette facon :
Code: Tout sélectionner
template <typename T> class Singleton
{
   private:
      static T* mInstance;
   public:
      Singleton()
      {
         assert(!mInstance);
         mInstance=(T*)this;
      }
      void ResetInstance() { mInstance=this; }
      static T* Get() {return mInstance;}
};


Et le code du plugin devient :
Code: Tout sélectionner
#include "Engine.h"
extern "C" void execute(Singleton<Manager>* pManager)
{
    pManager->ResetInstance();
    if(Manager::Get()) std::cout << "Cette fois le plugin connait le singleton" << std::endl;
}


Et a ce moment la ca marche. Mais
1. Ca fait un peu bricolage et je ne suis pas sur que ce comportement soit portable. Si quelqu'un compile ca, ca m'intéresse de savoir si ca marche sous windows.
2. Ca implique que je sache sous quel type le template du singleton est instancié, et donc que je fasse une fonction pour chaque type. Bref ca limite les singletons transmissibles a un set défini et compilé. Or j'aimerais un systeme qui soit extensible a d'autres types de singletons.
Avatar de l’utilisateur
Rewpparo
Hello World, I'm new !
 
Messages: 41
Inscription: 30 Nov 2007, 14:04
Localisation: La rochelle

Messagepar Thelvyn » 22 Oct 2008, 23:39

Bon je vois que tu désespères un peu, je vais te proposer ma solution ( que j'ai codé ce midi :p ).
On va voir si elle te convient :)

Mais tout d'abord je voulais te faire la remarque que d'après l'exemple que tu montres tu utilises un manager concret pour ton singleton. Mon avis serait d'utiliser que des interfaces dans un plugin ce qui permet de lier le moins possible le code.

Je vais donc présenter une méthode qui utilise un singleton template avec un type abstrait. Le défaut ( ou feature ? :p ) c'est que le singleton doit être initialisé avant son utilisation. Toute façon ça coûte pas cher.

l'interface qui sera exposé : ( friend non nécessaire ;) )
Code: Tout sélectionner
#ifndef _IFOO_HPP_
#define _IFOO_HPP_

#include <string>
#include "tsingleton.hpp"

class IFoo:
    public TSingleton<IFoo>
{
public:
    virtual void set(const std::string& a) = 0;
    virtual std::string get() = 0;

protected:
    IFoo() {}
    virtual ~IFoo() {}
};

#endif /* _IFOO_HPP_ */


singleton en template : ( il faut encore jouer un peu pour la destruction... )

Code: Tout sélectionner
#ifndef _TSINGLETON_HPP_
#define _TSINGLETON_HPP_

#include <cassert>


template<class T>
struct TSingletonInitializer
{};

template<class T>
class TSingleton
{
public:
    static T& instance();
    static T* instancePtr();

    template<class T2>
    static void initialize(const TSingletonInitializer<T2>& initializer);

protected:
    TSingleton();
    virtual ~TSingleton();
   
private:
    static T *sInstance;
};


template<class T>
T *TSingleton<T>::sInstance = 0;

template<class T>
TSingleton<T>::TSingleton()
{
}

template<class T>
TSingleton<T>::~TSingleton()
{
}

template<class T>
T& TSingleton<T>::instance()
{
    assert(sInstance != 0);
    return *sInstance;
}

template<class T>
T* TSingleton<T>::instancePtr()
{
    assert(sInstance != 0);
    return sInstance;
}

template<class T1>
template<class T2>
void TSingleton<T1>::initialize(const TSingletonInitializer<T2>&) {
    assert(sInstance == 0);
    sInstance = new T2();
}

#endif /* _TSINGLETON_HPP_ */


la classe concrète pour le singleton: ( ici le friend, ne peut être construit que par le singleton ;) )

Code: Tout sélectionner
#ifndef _FOO_HPP_
#define _FOO_HPP_

#include "ifoo.hpp"

class Foo:
    public IFoo
{
public:
    void set(const std::string& string);
    std::string get();
   
private:
    Foo();
    virtual ~Foo();

private:
    std::string myString;

    friend class TSingleton<IFoo>;
};

#endif /* _FOO_HPP_ */


le main qui va avec:

Code: Tout sélectionner
#include "foo.hpp"
#include <iostream>
#include <dlfcn.h>

int main(int argc, char *argv[])
{
    TSingleton<IFoo>::initialize( TSingletonInitializer<Foo>() ); // création
    IFoo& foo = IFoo::instance(); // on récup l'instance pour voir :)

    foo.set("blab");
    std::cout << "Main : " << foo.get() << std::endl;
    std::cout << " dlopen { " << std::endl;

    void *hnd = dlopen("plugs/libplugin1.so", RTLD_LAZY);
    if ( hnd == NULL ) {
   std::cout << dlerror() << std::endl;
        return -1;
    }

    std::cout << " } dlopen " << std::endl;

    // manque la destruction du singleton

    return 0;
}


et un petit plugin :p :

Code: Tout sélectionner
#include "../ifoo.hpp" // ne dépend que de l'interface :)
#include <iostream>

namespace magic_plug1 {

    class OnLoad
    {
    public:
   OnLoad() {
       std::cout << "ON LOAD PLUG 1 : " << IFoo::instance().get() << std::endl;
   }
    };   
    OnLoad magic;
}


l'avantage c'est que le plugin n'a besoin que de ifoo.hpp ( et donc de tsingleton.hpp ) pour compiler :)

voici les sources avec makefile ;) http://thelvyn32.free.fr/sing_plug.tar.bz2
( tar -xjvf sing_plug.tar.bz2 && cd sing_plug && make )

++
CoDito, ergo sum...
Avatar de l’utilisateur
Thelvyn
 
Messages: 488
Inscription: 31 Mai 2005, 20:40

Messagepar Rewpparo » 23 Oct 2008, 16:37

Wahou !
A voir l'exécution, c'est exactement ce que je cherche ! Et en plus c'est très élégant coté plugin ! Mais je ne comprend pas bien comment le plugin trouve la variable statique. Le plugin et le programme contiennent le symbole pour sInstance, donc a priori chacun a une instance, ce qui est cohérent avec le code. Je ne comprend pas comment les deux instances peuvent avoir la même valeur ? C'est du au fait que la déclaration de mInstance est dans le hpp ? Ou alors c'est lié a ce que tu fais avec les SingletonInitializer ? Comment le linker résout ça ?

Quand au truc avec les interfaces, je l'avais déjà vu faire, mais je ne comprenais pas vraiment a quoi ça servait. Maintenant je vois beaucoup mieux !

Merci beaucoup pour ce code, il m'aide énormément !
Avatar de l’utilisateur
Rewpparo
Hello World, I'm new !
 
Messages: 41
Inscription: 30 Nov 2007, 14:04
Localisation: La rochelle

Messagepar Rewpparo » 24 Oct 2008, 12:30

Depuis hier j'ai essayé de reproduire ce code, sans succès. Mais j'ai fini par réussir a isoler ce qui permet ce genre de comportement, apparemment c'est l'option -rdynamic passé au linker du programme principal, et qui permet de partager les variables statiques. Le probleme c'est que c'est pas portable. Apparemment sous IRIX ou SunOS cette option est tout simplement ignorée, et je ne connais pas d'équivalent sous visual. J'ai donc malheureusement peur de ne pas pouvoir m'appuyer dessus pour mon projet :(

Par contre je vais revoir mon design pour voir l'influence des interfaces comme tu le préconises, c'est vrai que ça peut être plus propre.
Avatar de l’utilisateur
Rewpparo
Hello World, I'm new !
 
Messages: 41
Inscription: 30 Nov 2007, 14:04
Localisation: La rochelle


Retourner vers Programmation

Qui est en ligne

Utilisateurs parcourant ce forum: Aucun utilisateur enregistré et 12 invités

cron