Léa-Linux & amis :   LinuxFR   GCU-Squad   GNU
Gestionnaire de plugin en C++
Envoyé par: oudoubah

Bonjour,

Je suis en train d'écrire un logiciel de dérawtisation et traitement de photos (https://gna.org/projects/negatif/) (Au passage, j'utilise les librairies qui correspondent à imagemagick et dcraw).

J'aimerais que l'application principal sache faire juste un minimum de choses, et que toutes ses fonctions de traitement soient faites sous forme de plugin (cela permettrait d'orienter le logiciel vers le besoin de tout un chacun, et faciliterait l'intégration de contributions, et permettrait à l'utilisateur de n'intégrer que ce dont il n'a pas besoin, et ainsi de rendre l'interface plus claire).

D'où ma question : comment-peut on écrire un gestionnaire de plugin?

Les deux pistes que j'ai commencé à étudier :
* le plugin est sous la forme d'un exécutable, mais comment peut-on partager les données?
* le plugin est sous la forme d'une librairie, mais comment effectuer le chargement explicite de plugin1.so et plugin2.so?

Tu as lu les docs. Tu es devenu un informaticien. Que tu le veuilles
ou non. Lire la doc, c'est le Premier et Unique Commandement de
l'informaticien.
-+- TP in: Guide du Linuxien pervers - "L'évangile selon St Thomas"

Poste le Tuesday 16 December 2008 18:07:31
Répondre     Citer    
Re: Gestionnaire de plugin en C++

Citation
oudoubah
le plugin est sous la forme d'un exécutable, mais comment peut-on partager les données?

A mon avis, ce n'est plus alors un plugin. On pourrait toujours partager les données (péniblement) par un fichier commun projeté en mémoire par mmap, ou bien par de la mémoire partagé par shmget etc.

Citation
oudoubah
le plugin est sous la forme d'une librairie, mais comment effectuer le chargement explicite de plugin1.so et plugin2.so?

En utilisant la fonction dlopen (et donc la librarie de chargement dynamique -ldl). Le plugin doit alors être un objet dynamique partagé, càd une librarie *.so compilé avec -fPIC -shared.

Dans mon esprit, un plugin est nécessairement quelque chose qui est chargé par dlopen (ou bien par une fonction qui s'appuie dessus). Voir aussi Shared Library (Program Library Howto)

Notes que Qt et Gtk offrent tous les deux un support de plugin, par dessus dlopen. Dans Qt4 voir QPluginLoader QLibrary etc. Dans Gtk, voir GModule g_module_open (fonctions de Glib) etc..

----

Basile STARYNKEVITCH

Membre de l'APRIL « promouvoir et défendre le logiciel libre » - adhérez vous aussi à l'APRIL!

Projet logiciel libre: RefPerSys

Poste le Tuesday 16 December 2008 18:22:33
Répondre     Citer    
Re: Gestionnaire de plugin en C++
Envoyé par: oudoubah

Merci pour ces premières informations.

Pour essayer de travailler en C++, il ne resterait donc qu'à instancier un nouveau plugin dans la fonction onload() du plugin.so?

Pour l'interface graphique, j'utilise fltk, qui ne possède que peu de fonctions "hors interface graphique" mais que je trouve très bien (je préfère utiliser directement les librairies de base pour tout ce qui n'est pas IHM : pthreads et libdl maintenant).

Tu as lu les docs. Tu es devenu un informaticien. Que tu le veuilles
ou non. Lire la doc, c'est le Premier et Unique Commandement de
l'informaticien.
-+- TP in: Guide du Linuxien pervers - "L'évangile selon St Thomas"

Poste le Tuesday 16 December 2008 19:39:03
Répondre     Citer    
Re: Gestionnaire de plugin en C++

En pratique, un point important quand on appelle (via dlsym) une fonction dans un plugin qui a été dlopen-é. Il faut que la fonction ait un nom simple (non "mangled") donc en C++ il faut la déclarer avec par exemple
extern "C" int mafonction(void*);
dans le plugin, et dans le programme principal quelque chose du genre de
int (*ptrfun)(void*); // declarer le pointeur de fonction
ptrfun = dlsym(plugindlh, "mafonction");
if (!ptrfun) {perror(dlerror()); exit(1); };
int r = ptrfun(ad);
en ayant fait préalablement
plugindlh = dlopen("monfoo.so", RTLD_NOW);

----

Basile STARYNKEVITCH

Membre de l'APRIL « promouvoir et défendre le logiciel libre » - adhérez vous aussi à l'APRIL!

Projet logiciel libre: RefPerSys

Poste le Tuesday 16 December 2008 20:24:40
Répondre     Citer    
Re: Gestionnaire de plugin en C++

Citation
oudoubah
Pour essayer de travailler en C++, il ne resterait donc qu'à instancier un nouveau plugin dans la fonction onload() du plugin.so?

Pas si tu travailles au niveau de dlopen en tout cas.

D'abord, je ne comprends pas bien le verbe "instancier un nouveau plugin" dans ta phrase. On n'instancie pas un plugin (à partir de quelque chose qui en serait le patron ou le moule), on le charge. Le plugin existe préalablement: il a dû être compilé, à partir de son code source en C++, avec
g++ -fPIC -shared plugin.cc  -O -o plugin.so
Ensuite tu le charges (une fois) dans ton programme avec
dlh = dlopen("./plugin.so", RTLD_NOW);
(mais lis bien la page de man de dlopen, il y a des subtilités importantes). Au moment du chargement -par l'appel à dlopen-, les fonctions constructeurs (càd les constructeurs de données statiques C++, et les fonctions C déclarées avec attribute((constructor)) comme indiqué ici) sont exécutées.

Ensuite, c'est à toi de définir et de documenter le protocole d'usage de ton plugin. Tu peux par exemple décider qu'il doit fournir une fonction dont la signature est
extern "C" int fillwindow(fltk::Window*);
il faut que ça soit un extern "C". Tu dois documenter et définir la sémantique de cette fonction.

Tu vas alors chercher par dlsym le pointeur vers cette fonction, avec
int (*fillwinptr)(fltk::Window*);
fillwinptr = dlsym(dlh, "fillwindow");
if (!fillwinptr) {cerr << "dlsym failed << dlerror() << endl; exit(1);};

Quand tu as besoin de cette fonction, tu l'appelles via son pointeur.

Enfin, tu peux décharger le plugin à la fin avec dlclose (qui ferait exécuter les fonctions destructeurs du plugin).

Si tu voulais (je n'ai pas compris pourquoi tu veux des plugins) charger un code particulier à chaque exécution, tu pourrais par exemple générer ce code en C ou C++, le compiler par un appel system(3) à g++, puis le charger. Tu pourrais aussi méta-programmer ainsi avec libjit ou llvm ou GNU lightning. Avec de telles approches (intéressantes mais difficiles) il faudrait passer par un AST (abstract syntax tree). Mais je ne crois pas que tu veuilles faire ça (à dire vrai, je n'ai pas compris quelle est ta motivation pour les plugins).

En espérant t'avoir aidé!

----

Basile STARYNKEVITCH

Membre de l'APRIL « promouvoir et défendre le logiciel libre » - adhérez vous aussi à l'APRIL!

Projet logiciel libre: RefPerSys

Poste le Tuesday 16 December 2008 21:25:46
Répondre     Citer    
Re: Gestionnaire de plugin en C++
Envoyé par: oudoubah

Citation
Basile STARYNKEVITCH
D'abord, je ne comprends pas bien le verbe
"instancier un nouveau plugin" dans ta phrase. On
n'instancie pas un plugin (à partir de quelque
chose qui en serait le patron ou le moule), on le
charge.

C'est une petite erreur de relecture O:-)
Je voulais dire instancier un objet.

Citation
Basile STARYNKEVITCH
Le plugin existe préalablement: il a dû
être compilé, à partir de son code source en C++,
avecg++ -fPIC -shared plugin.cc -O -o plugin.so
Ensuite tu le charges (une fois) dans ton
programme avecdlh = dlopen("./plugin.so",
RTLD_NOW);(mais lis bien la page de man de dlopen,
il y a des subtilités importantes).
Jusque là, j'y arrive (il me semble que RTLD_NOW soit dans mon cas mieux que RTLD_LAZY, mais je ne saisis pas vraiment dans quel cas RTLD_LAZY est plus approprié.)


Citation
Basile STARYNKEVITCH
il faut que ça soit un
extern "C". Tu dois documenter et définir la
sémantique de cette fonction.

Tu vas alors chercher par dlsym le pointeur vers
cette fonction, avecint
(*fillwinptr)(fltk::Window*);
fillwinptr = dlsym(dlh, "fillwindow");
if (!fillwinptr) {cerr << "dlsym failed
<< dlerror() << endl; exit(1);};

Quand tu as besoin de cette fonction, tu
l'appelles via son pointeur.

Je vois le principe dont tu me parles. La procédure est plus complexe à mettre en place que mon idéal. Le comportement que j'aimerais avoir est le suivant (en algorithme) :

Plugin est une classe qui possède une méthode run();
main possède un objet "gui" de portée globale (il a été défini avant le main()).
gui possède un élément de type Plugin* (gui->plugin)

1) demande de chargement du plugin
2) constructeur du plugin : création d'un nouvel objet :
Plugin *monplugin=new Plugin();
3) affectation de monplugin à l'objet principal : gui->plugin=monplugin (là, ça ne fonctionne pas)
4) fin du constructeur du plugin
5) appel de gui->plugion->run();

Si selon toi ce comportement n'est pas possible, je me rabattrai sur l'appel simple de fonction.

Enfin, tu peux décharger le plugin à la fin avec
dlclose (qui ferait exécuter les fonctions
destructeurs du plugin).

Citation
Basile STARYNKEVITCH
Si tu voulais (je n'ai pas compris pourquoi tu
veux des plugins) charger un code particulier à
chaque exécution, tu pourrais par exemple générer
ce code en C ou C++, le compiler par un appel
system(3) à g++, puis le charger. Tu pourrais
aussi méta-programmer ainsi avec libjit ou llvm ou
GNU lightning. Avec de telles approches
(intéressantes mais difficiles) il faudrait passer
par un AST (abstract syntax tree). Mais je ne
crois pas que tu veuilles faire ça (à dire vrai,
je n'ai pas compris quelle est ta motivation pour
les plugins).

Voici toute l'histoire : je fais un peu de photo, et je n'ai pas trouvé de logiciel open source permettant de les travailler avec un seul outil, et ayant une interface optimisée pour le traitement des photos. Pour moi, Digikam est inexploitable (on passe trop de temps à aller dans les menus pour changer les paramètres de base d'une photo), Rawstudio pourrait être bien, mais il a des défauts de conception : il perdent du temps à réécrire ce qui existe déjà, et moins bien (ils ont codés eux-même les rotations, par palier de 90°. On ne peut pas redresser une photo de quelques degrés ; ils ont recodé les fonctions de contraste, luminosité,...). Par contre, il a d'autres fonctions très très intéressantes (mais il manque trop de choses pour être utilisable pour moi).

Pour éviter de réinventer la roue, j'utilise la librairie libraw pour ouvrir les fichiers au format raw (cette partie aurait été trop compliquée pour moi, les spécifications des fichiers étant inexistante) ainsi que magick++ (la version librairie C++ de Image Magick, qui peut faire beaucoup de travail).
Je me suis rajouté des contraintes pour tenter de bien définir les fonctions de bases :
* utilisation de librairies existantes pour :
** avoir moins de code à maintenir
** gagner du temps de développement (par exemple, une rotation se traduit par un simple image.rotate(double degree).
* multithreading pour la création des imagettes (le nombre de threads est configurable. Cela permet sur ma machine d'ouvrir 20 fichiers en parallèle en utilisant 1Go de RAM. C'est gourmand, mais donne de bonnes performances. L'ouverture de fichiers raw implique des calculs qui sont plus long que le temps de lecture d'un fichier)
* multilingue (cette partie là n'est pas encore abordée)
* basée sur des plugins pour les outils de traitement d'image. L'utilisation de plugins permettra plusieurs choses :
** l'utilisateur pourra ne charger que les fonctions dont il a besoin : son interface ne sera pas chargée de fonctions qui ne lui servent pas
** l'utilisateur pourra ordonner le chargement des plugins : l'interface sera adaptée à sa façon de travailler
** un développeur pourra écrire son propre plugin et le diffuser. Ce sera plus simple pour motiver les contributions : pas besoin de modifier le programme principal pour rajouter des fonctionnalités.

Bref, je complexifie au départ pour pouvoir travailler plus vite par la suite.

Tu as lu les docs. Tu es devenu un informaticien. Que tu le veuilles
ou non. Lire la doc, c'est le Premier et Unique Commandement de
l'informaticien.
-+- TP in: Guide du Linuxien pervers - "L'évangile selon St Thomas"

Poste le Tuesday 16 December 2008 22:54:57
Répondre     Citer    
Re: Gestionnaire de plugin en C++

Je n'ai pas vraiment compris ce que tu appelles Plugin.

La librarie libdl.so (càd -ldl) t'offre les fonctionnalités suivantes:

* l'appel dlopen charge une librairie partagée externe, en s'appuyant sur l'appel système mmap, puis en faisant une édition de liens dynamiques. En effet le code chargé peut appeler des fonctions du programme principal (qui doit alors être linké avec l'option -rdynamic). dlopen renvoie un pointeur opaque vers un descripteur de librairie chargée. L'appel dlopen agrège donc du nouveau code dans ton programme! Les fonctions d'attribut constructeur de la librairie chargée sont appelées au moment du dlopen

* l'appel dlsym recherche (dans un descripteur de librairie chargée obtenu par dlopen) un symbole (au sens du linker) dans cette librairie chargée et renvoie son adresse. Il te convient de définir ce que ce symbole peut être et à quoi il va servir. Ce pourrait être un nom de fonction ou de données. Au niveau du linker, les méthodes C++ n'existent pas (g++ les compile en des fonctions). D'ailleurs même la notion de fonction n'existe pas vraiment, c'est juste un symbole dont l'adresse associée est dans le segment de code.

Donc je ne comprends pas que tu me parles de méthode ou d'objet. Au niveau du linker (et aussi du chargeur dynamique), ça n'existe pas: une librairie dynamique contient juste des segments (celui du code et celui des données) qui sont projetés en mémoire par des appels mmap depuis dlopen) et une table des symboles (où des noms sont associés à des adresses) et des ordres de relocations (où l'on demande au chargeur de modifier tel bout du segment de code ou de données en utilisant tel nom).

Il me semble que tu as oublié un point important: les notions de méthode et d'objet sont des notions linguistiques: elles n'existent que dans le code source, pas dans le code binaire (celui d'une librairie chargée par dlopen ou celui d'un exécutable).

Le code binaire (dans une librairie dynamique) chargé par dlopen peut avoir été compilé par n'importe quel compilateur ou obtenu comme tu le veux. dlopen s'occupe juste de le charger dans l'espace de ton processus et de résoudre les liens (le chargement dynamique). Et dlsym te permet ensuite de retrouver une adresse à partir d'un nom. Tu en fais ensuite ce que tu veux.

Par contre, tu ne pourras probablement pas facilement charger une librarie pré-existante, car en pratique tu dois définir un protocole et des conventions spécifiques à ton programme. Une librarie peut être compilée qui dépend d'une autre, tu pourrais compiler ton plugin avec par exemple
g++ -fPIC -shared -O -g tonplugin.cc -o tonplugin.so -lMagick -ljpeg

D'ailleurs un plugin peut être obtenu en linkant (avec -shared) plusieurs objets *.pic.o compilés avec -fPIC

Bref, je n'ai pas compris ce que tu veux. Expliques moi en parlant juste de nom et d'adresses, pas en parlant de méthodes ou d'objets qui n'existent plus dans le code binaire. Au moment du chargement dynamique par dlopen, n'existe que des adresses, des noms, et des ordres de relocation. Par exemple, si le code que tu as chargé contient un appel à exit, l'ordre de relocation (contenant une référence au symbole exit) fera modifier le segment de code pour y mettre à un endroit précis l'adresse de exit.

Lis aussi le C++ dlopen mini howto.

----

Basile STARYNKEVITCH

Membre de l'APRIL « promouvoir et défendre le logiciel libre » - adhérez vous aussi à l'APRIL!

Projet logiciel libre: RefPerSys

Poste le Wednesday 17 December 2008 08:16:08
Répondre     Citer    
Re: Gestionnaire de plugin en C++
Envoyé par: oudoubah

Citation
Basile STARYNKEVITCH
Lis aussi le C++ dlopen mini howto.

C'est exactement ce qui me faut (surtout la partie Loading Classes). Merci!

Honnêtement, j'ai du mal à transcrire une représentation objet en language bas niveau.

Tu as lu les docs. Tu es devenu un informaticien. Que tu le veuilles
ou non. Lire la doc, c'est le Premier et Unique Commandement de
l'informaticien.
-+- TP in: Guide du Linuxien pervers - "L'évangile selon St Thomas"

Poste le Wednesday 17 December 2008 16:40:40
Répondre     Citer    
Re: Gestionnaire de plugin en C++

Citation
oudoubah
[v]En parlant du C++ dlopen minihowto[/v]
C'est exactement ce qui me faut (surtout la partie Loading Classes). Merci!

Honnêtement, j'ai du mal à transcrire une représentation objet en language bas niveau.

La partie Loading Classes te raconte la même chose que moi. Tu ne peux raisonnablement appeler qu'une fonction déclarée extern "C". Donc si tu veux que ton plugin crée des objets utilisés par le programme principal, il faut que le plugin exporte une fonction de création.

D'ailleurs, il faut comprendre qu'un plugin fonctionne nécessairement par indirection via des noms, et que ton programme peut avoir plusieurs plugins; par expérience, tu peux dlopen-er des centaines de milliers de plugins différents, et probablement bien plus: la seule limitation est la mémoire [virtuelle] occupée par le code.... Ma page contient un petit exemple illustratif (manydl.c).
Or, il n'est pas possible (car il n'y a pas de construction syntaxique pour ça en C++, parce qu'il n'y a pas de métaclasses en C++) de créer avec new une instance d'une classe que tu ne connais pas avant l'exécution. Ce n'est donc pas une limitation de dlopen, mais du langage C++ qui n'offre pas de notion de pointeur (ou référence) vers classe (dont l'implémentation serait un pointeur vers la vtable de la classe). Tu ne peux pas écrire en C++ quelque chose du genre de
/* code illicite en C++ actuel */
// charger dynamiquement la classe
Class* pointeur_de_classe = dlsym(dlh, "MaClasse");
// construire une instance de la classe dynamiquement chargée
void* pointeur_d_instance = new (*pointeur_de_classe)(123);
(ce code ne compile pas)
Et c'est pourtant ce que tu aurais envie d'écrire.

La limitation est donc linguistique. En réalité, C++ est un langage très pauvre (mais inutilement complexe).
[v]tu auras deviné que je n'aime pas C++.[/v]
D'ailleurs si Qt (mais aussi Fox etc...) a un mécanisme de métaclasse par dessus C++, ce n'est pas pour rien. C'est vraiement utile (et ça demande un support linguistique du compilateur, par exemple le moc de Qt).

Si tu cherches un langage dont les classes sont réifiées en des objets, regardes Java, Ruby, Common Lisp, SmallTalk et autres.

Si ces aspects linguistiques t'intéressent je te conseille de lire l'excellent livre (pas cher, en français) de Christian Queinnec: Principes d'implantation de Scheme et Lisp

En réalité, tu te heurtes donc non pas aux insuffisances de libdl.so (qui gère le minimum suffisant: chargement de plugin par dlopen et résolution de nom par dlsym) mais à un abominable manquement de C++.
Tu pourrais aussi convenir qu'une fonction d'attribute((constructor)) de ton plugin enregistre auprès de ton programme principal, par exemple dans un dictionnaire ou table de hash, les fonctions de constructions d'instance. En faisant ça, tu réinventes un mini mécanisme de méta-classe du pauvre qui fait tant défaut à C++.

Pour info, la toute dernière version d'Ocaml 3.11 fournit aussi le chargement dynamique de code natif.

Moi, j'ai du mal à comprendre comment on peut coder des objets sans métaclasse, cad en C++. La première chose que je définis quand je spécifie puis implémente un langage à objets et classes (comme MELT par exemple), c'est son mécanisme de métaclasse (en m'inspirant d' ObjvLisp).


----

Basile STARYNKEVITCH

Membre de l'APRIL « promouvoir et défendre le logiciel libre » - adhérez vous aussi à l'APRIL!

Projet logiciel libre: RefPerSys

Poste le Wednesday 17 December 2008 17:20:17
Répondre     Citer    

Veuillez vous authentifier auparavant pour commenter.

 

Ce forum !
Gestionnaire de plugin en C++
Pour poser vos questions sur les scripts shell, le Perl, le C, etc... Attention : nous ne sommes pas des spécialistes du dev, ce forum est juste pour de petites aides ponctuelles concernant le développement et les outils de développement.

Sauf mention contraire, les documentations publiées sont sous licence Creative-Commons