Bonjour,
Après avoir fait en sorte que le Kernel puisse charger Module Manager, il faut que je fasse en sorte que Module Manager puisse charger tous les autres modules.
Pour rappel, un module est, ici, un ensemble de fonctions contenues dans un fichier de bibliothèque dynamique (*.so ou *.dll), que je charge dynamiquement au sein du moteur.
Un module a donc :
- un nom
- une version
- une interface (ensemble des prototype des fonctions qui peuvent être utilisés).
- etc.
On peut alors remarquer qu'il y a deux types d'informations contenus dans le module :
- InfoModule, dont la structure est identique à tous les modules (chaque module a un nom, une version, etc.)
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 struct InfoModule { std::string nom; Version version; }- InterfaceModule, dont la structure change en fonction du module (tous les modules n'offrent pas les même fonctionnalités ).
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 struct InterfaceModule { void fonction1(void); void fonction2(void); // autres fonctions offerte par le module }
Au chargement du module, il faut donc donner toutes ces informations a Module Manager et aux autres modules susceptibles d'utiliser le module ainsi chargé (qu'on nommera modules utilisateurs).
On sait que chaque modules utilisateurs doit connaitre l'interface proposé par le module chargé pour pouvoir l'utiliser. Ce qui parait assez logique, pour utiliser une fonction "drawImage", les modules utilisateurs doivent savoir qu'elle existe et quel est son prototype.
On peut donc inclure dans chaque modules utilisateurs un fichier d'en-tête décrivant l'interface proposée par le module chargé. Ceci forcera aussi de garder une certaine rétrocompatibilité des modules.
Une autre solution serait de décrire chaque fonctions proposées par l'interface en associant à un nom de fonction, des informations sur ladite fonction :
Mais cette méthode pose deux problèmes :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 struct InfoInterface {} std::map<std::string, InfoFonction> interface;
- il va falloir remplir cette structure "à la main", on va dupliquer des informations, bref cela prend du temps et peut être source de données obsolètes.
- le passage des paramètres à la fonction va être source de problèmes
- soit il va falloir caster le pointeur de fonction contenu dans InfoFonction
- soit il va falloir créer un prototype de fonction "générique" prenant toujours les même paramètres, ce qui devient assez lourd et peu pratique.
Donc Module Manager, en chargeant le module, doit récupérer un objet contenant :
- une instance implémentant InterfaceModule
- une instance de InfoModule
Sachant que les modules utilisateurs connaissent le prototype de InterfaceModule.
On a alors deux choix pour transmettre ces deux instances :
- créer une classe ModuleBase contenant les informations InfoModule et dont tous les modules hériterons :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9 class ModuleBase { std::string nom; Version version; } class ModuleA : public ModuleBase { void fonctionA(void); // etc.... }- inclure dans une structure InfoModule un pointeur vers l'instance d'InfoModule :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10 typedef void * InterfaceModule; class InterfaceModuleA { } class InfoModule { std::string nom; version version; InterfaceModule module; }
Toujours est-il qu'on ne pourra pas éviter de caster à moment ou à un autre ModuleBase ou InterfaceModule.
Je préfère éviter d'utiliser l'héritage car elle n'apporte pas grand chose ici par rapport à la deuxième solution.
Mais le fait de devoir caster est un peu embêtant : on a aucune garantie que les modules utilisateurs et le module chargé ai la même définition de l'interface module :
Pour éviter cela, il faut qu'on puisse attribuer à chaque InterfaceModule une clé unique composée :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11 class InterfaceModuleA{ bool foo(void); } class InterfaceModuleB { void test(std::string); } InterfaceModuleB interface; InterfaceModuleA interfaceA= (InterfaceModuleA)interface; interfaceA.foo(); // erreur !
- d'une clé unique identifiant le module
- d'un numéro de version (pour différencier les différentes versions d'un même module)
Pour le numéro de version, un simple entier qu'on incrémente à chaque version suffit.
Pour l'identifiant du module, c'est un peu plus compliqué. Il faut aussi forcer les utilisateurs à changer son identifiant lorsqu'ils le forkent.
L'identifiant du module pourrait être composé :
- d'un identifiant spécifique à l'auteur du module, pour cela on peut penser :
- soit à une URL de dépôt git/github
- soit à une adresse mail de l'auteur
- un entier
L'identifiant spécifique à l'auteur est donc normalement unique et permet en plus de pouvoir le contacter en cas de problèmes de plus il changera à chaque fork du module.
A partir de là, chaque auteur peut être garant de l'unicité de la seconde partie de l'identifiant (timestamp ou autre).
De là, il suffit d'ajouter les InfoModules dans les modules utilisateurs et d'y ajouter quelques fonctions :
Ainsi, on peut vérifier que les InterfaceModule des modules utilisateurs et des modules utilisés sont compatibles.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 class InfoModule { const std::string & moduleURL(void) const { const static std::string url = "http://github.com/User/module"; return url; } const std::string & version(void) const { const static Version version = 0; return version; } bool compatible( const InfoModule & ) { return false; } }
Je fais quelques essais, je teste quelques pistes pour améliorer la communication.
J'ai essayé de proposer une problèmatique à laquelle je suis confronté et d'y réfléchir ici en donnant la solution que j'envisage tout en tentant de la justifier.
Je ne perd pas vraiment de temps car cela m'oblige à bien réfléchir sur le problème rencontré, de ne pas me précipiter et le fait de l'écrire me donne de nouvelles idées.
Je ne trouve pas que cela est inintéressant, mais pas très adapté à ce média je trouve.
Peut-être qu'un blog DVP serait plus adapté à mon style de communication (?), malheureusement, je n'ai pas l'impression qu'il soit pratique pour les lecteurs de suivre ces blogs ?
Je continu de réfléchir un peu
Partager