OVH Cloud OVH Cloud

Chargement dynamique de plugins/librairies

28 réponses
Avatar
Delf
Bonjour.

Mon application doit charger à l'exécution un ensemble inconnu de
plugins situés dans un répertoire donné.

Ces plugins sont aussi écrits en C++ et implémentent une interface IPlugins.

Je compile les librairies en utilisant la commande suivante :

g++ -shared -I /usr/local/include/libxml2/ -I /usr/local/include/
fichier.cpp -o fichier.so

Ma question, comment récupérer dans le répertoire donné tous les .so,
s'assurer qu'il s'agit bien de plugins (on peut avoir un fichier texte
nommé xxx.so), charger chaque plugins et lancer les méthodes ?

Ca fait beaucoup de questions...

J'ai pu voir dlopen() et extern "C" (mais pour les librairies en C...).

Pour dlopen(), j'utilise le code suivant :

void* handle = dlopen("./plugins/test.so", RTLD_LAZY);

if (handle == NULL)
{
std::cout << dlerror() << std::endl;

exit(1);
}

En sortie : ./plugins/test.so: Undefined symbol "_ZN8IPluginsC2Ev"

Par ailleurs, on m'a dit que dlopen() ne servait pas dans mon cas...
Bref, suis un peu dans le vague.
Merci anticipé.

--
Delf
Do not use this email in Cc!
L'alcool tue lentement. On s'en fout. On n'est pas pressé.

10 réponses

1 2 3
Avatar
Delf
Delf wrote:

[...]


http://www.faqs.org/docs/Linux-mini/C++-dlopen.html

J'ai trouvé ce lien, à première vue, c'est ce que je souhaite faire.

--
Delf
Do not use this email in Cc!
A New York les taxis sont jaunes, à Londres ils sont noirs et à Paris
ils sont cons.

Avatar
Delf
Delf wrote:

[...]


Je ne m'en sors pas :
J'ai suivi la documentation :
http://www.faqs.org/docs/Linux-mini/C++-dlopen.html

Mes plugins (ici CTest) héritent de la classe Plugins (tiens, je pensais
l'avoir nommé CPlugins).

Plugins.h:

#ifndef _PLUGINS_H
#define _PLUGINS_H

#include <string>

class Plugins
{
public:

enum LoadStatus
{
...
};

enum LaunchStatus
{
...
};

public:

Plugins();

virtual ~Plugins();

Plugins::LoadStatus Load(const std::string& /* file */);

virtual bool IsRightPlugins(const std::string /* request */) const = 0;

virtual Plugins::LaunchStatus Launch(const std::string& /* request */)
const = 0;
};

typedef Plugins* CreatePlugins();

typedef void DestroyPlugins(Plugins*);

#endif // _PLUGINS_H

Plugins.cpp:

#include "Plugins.h"

...

#ifdef DEBUG
#include <iostream>
#endif // DEBUG

Plugins::Plugins()
{
#ifdef DEBUG
std::cout << "Plugins::Plugins()" << std::endl;
#endif // DEBUG
}

Plugins::~Plugins()
{
#ifdef DEBUG
std::cout << "Plugins::~Plugins()" << std::endl;
#endif // DEBUG
}

Plugins::LoadStatus Plugins::Load(const std::string& file)
{
#ifdef DEBUG
std::cout << "Plugins::Load()" << std::endl;
#endif // DEBUG

return Plugins::CORRECT;
}

Test.h:

#ifndef _TEST_H
#define _TEST_H

#include <string>

#include "../Plugins.h" // FIX -> "Plugins.h"

class CTest : public Plugins
{
public:

virtual bool IsRightPlugins(const std::string /* request */) const;

virtual Plugins::LaunchStatus Launch(const std::string /* request */) const;
};

#endif // _TEST_H

Test.cpp:

#include "Test.h"

...

#ifdef DEBUG
#include <iostream>
#endif // DEBUG

bool CTest::IsRightPlugins(const std::string request) const
{
#ifdef DEBUG
std::cout << "CTest::IsRightPlugins(" << request << ")" << std::endl;
#endif // DEBUG

return true;
}

Plugins::LaunchStatus CTest::Launch(const std::string request) const
{
#ifdef DEBUG
std::cout << "CTest::Launch(" << request << ")" << std::endl;
#endif // DEBUG

return Plugins::DONE;
}

extern "C" Plugins* Create()
{
return new CTest;
}

extern "C" void Destroy(Plugins* plugins)
{
delete plugins;
}

Je compile la librairie test.so :

g++ -shared -fPIC plugins/Test.cpp -o plugins/test.so -I
/usr/local/include/libxml2/ -I /usr/local/include/

->

[22:08][ src]# make libs
g++ -shared -fPIC plugins/Test.cpp -o plugins/test.so -I
/usr/local/include/libxml2/ -I /usr/local/include/

plugins/Test.cpp: In function `Plugins* Create()':
plugins/Test.cpp:72: error: cannot allocate an object of type `CTest'
plugins/Test.cpp:72: error: because the following virtual functions
are abstract:
plugins/../Plugins.h:74: error: virtual Plugins::LaunchStatus
Plugins::Launch(const std::string&) const
*** Error code 1

Stop in /usr/home/delf/lanctld/src.
[22:08][ src]#

Est ce que quelqu'un peut m'aider ? Merci.

--
Delf
Do not use this email in Cc!
L'alcool tue lentement. On s'en fout. On n'est pas pressé.

Avatar
Sylvain
Delf wrote on 13/05/2006 22:16:
Delf wrote:

[...]


[22:08][ src]# make libs
g++ -shared -fPIC plugins/Test.cpp -o plugins/test.so -I
/usr/local/include/libxml2/ -I /usr/local/include/

plugins/Test.cpp: In function `Plugins* Create()':
plugins/Test.cpp:72: error: cannot allocate an object of type `CTest'
plugins/Test.cpp:72: error: because the following virtual functions
are abstract:
plugins/../Plugins.h:74: error: virtual Plugins::LaunchStatus
Plugins::Launch(const std::string&) const
*** Error code 1


l'erreur est (assez) claire: la lib ne se compile pas car la fct Create
tente d'instancier un CTest qui n'implémente pas toutes ses virtuelles.

et en effet, à la définition:

virtual LaunchStatus Launch(const std::string&) const = 0;

correspond l'implémentation

LaunchStatus CTest::Launch(const std::string rqst) const {}

où "string" n'est pas "string&".


sur le modèle lui-même, mille (ou presque) écritures sont possibles,
mais il peut être élégant de n'exporter qu'une seule statique C servant
de factory - un extern "C" Plugins* getInstance() - la libération de
cette instance pouvant se faire par une méthode d'instance
(par ex: void Plugin::release() { delete this; } ).

Sylvain.


Avatar
Delf
Sylvain wrote:

où "string" n'est pas "string&".


Argh ! J'avais pas vu que j'avais oublié des '&'... Tant de temps perdu
sur ça :') Merci

sur le modèle lui-même, mille (ou presque) écritures sont possibles,
mais il peut être élégant de n'exporter qu'une seule statique C servant
de factory - un extern "C" Plugins* getInstance() - la libération de
cette instance pouvant se faire par une méthode d'instance
(par ex: void Plugin::release() { delete this; } ).


Je n'ai pas saisi.

--
Delf
Do not use this email in Cc!
Tout bonheur commence par un petit déjeuner tranquille.

Avatar
Delf
Delf wrote:

Je compile la librairie test.so :

g++ -shared -fPIC plugins/Test.cpp -o plugins/test.so -I
/usr/local/include/libxml2/ -I /usr/local/include/


Lors de l'exécution du binaire, j'obtenais toujours le message :
undefined symbol

Il faut ajouter -rdynamic dans la commande g++ de construction du binaire.

Hop, tout semble fonctionner :]

--
Delf
Do not use this email in Cc!
On ne va jamais si loin que lorsque l'on ne sait pas où l'on va.

Avatar
Sylvain
Delf wrote on 13/05/2006 23:14:

sur le modèle lui-même, mille (ou presque) écritures sont possibles,
mais il peut être élégant de n'exporter qu'une seule statique C
servant de factory - un extern "C" Plugins* getInstance() - la
libération de cette instance pouvant se faire par une méthode d'instance
(par ex: void Plugin::release() { delete this; } ).


Je n'ai pas saisi.



ton modèle est:

appli linkée avec la déclaration:

class Plugins {
public:
Plugins();
virtual ~Plugins();
LoadStatus Load(const std::string&);
virtual bool IsRightPlugins(const std::string) const = null;
virtual LaunchStatus Launch(const std::string&) const = null;
};

et les libraries plug-ins exportent les symboles:

extern "C" Plugins* Create() { ... }
extern "C" void Destroy(Plugins* plugins) { ... }

mon point était:

Plugins peut définir:

class Plugins {
public:
Plugins();
virtual ~Plugins();
...
void release() { doDelete(); }
private:
virtual void doDelete() = null;
};

où doDelete est implémenté pour invoquer chaque destructeur virtuel
(donc il y aura un CTest::doDelete(){ delete this; }).

ceci permettra à l'appli de libérer un plug-in par plugin->release() au
lieu de récupérer l'adresse de la fct C statique Destroy et de l'appeler
- tu devras déjà conserver un tableau de fonction des factories
("Create" dans ton exemple, "getInstance" dans mon propos), il est peut
être intéressant de ne pas avoir en plus un tableau de Destroy'ers.


btw, dans plugins.h, les 2 typedef:
typedef Plugins* CreatePlugins();
typedef void DestroyPlugins(Plugins*);

ne veulent rien dire, ce n'est ni un export de fct, ni un nommage de fct.

la méthode "IsRightPlugins" est également byzarre, au début de ton
propos tu t'interrogais sur les moyens de détecter un vrai binaire
plug-in (vs un n_importe_quoi.so), le schéma d'export doit suffir à
cela: si le module exporte une fonction "Plugins* Create()" alors c'est
une lib. valide; dès lors demander à l'objet s'il est valide via une
méthode d'instance parait un peu byzarre (un dynamic_cast éventuel sur
le pointeur retourné par la lib. peut être utilisé pour renforcer le
controle du type de l'objet créé).

Sylvain.


Avatar
Delf
Sylvain wrote:

ceci permettra à l'appli de libérer un plug-in par plugin->release() au
lieu de récupérer l'adresse de la fct C statique Destroy et de l'appeler
- tu devras déjà conserver un tableau de fonction des factories
("Create" dans ton exemple, "getInstance" dans mon propos), il est peut
être intéressant de ne pas avoir en plus un tableau de Destroy'ers.


Je vais regarder ça de plus près dans les prochains jours.


la méthode "IsRightPlugins" est également byzarre


Elle ne sert pas à ce que tu crois :] Grosso-modo, elle me permettra de
savoir s'il s'agit du bon plugin à lancer en fonction de la requête du
client, rien à voir avec ma question précédente :)

Merci encore pour ton aide.
Dodo +..+

--
Delf
Do not use this email in Cc!
L'homme n'est que poussière. La femme est aspirateur.

Avatar
James Kanze
Delf wrote:

Mon application doit charger à l'exécution un ensemble inconnu de
plugins situés dans un répertoire donné.


Ces plugins sont aussi écrits en C++ et implémentent une interface
IPlugins.


Je compile les librairies en utilisant la commande suivante :


g++ -shared -I /usr/local/include/libxml2/ -I /usr/local/include/
fichier.cpp -o fichier.so


Ma question, comment récupérer dans le répertoire donné tous
les .so, s'assurer qu'il s'agit bien de plugins (on peut avoir
un fichier texte nommé xxx.so), charger chaque plugins et
lancer les méthodes ?



La réponse officielle ici, c'est que tu ne peux pas -- il n'y a
pas de plugin en C++, et même pas de répertoire, et dans la
pratique, ça dépend de la plateforme. Dans la pratique, pour
lire le répertoire, il existe des solutions portable, comme
boost::filesystem -- mais les requêtes de base dépendent aussi
de la plateforme (opendir, readdir, et closedir sous des Unix et
semblables). En ce qui concerne le chargement du plugin et la
récherche de ses points d'entrée, je ne connais même pas de
solution portable : les fonctions s'appelle dlopen, dlsym et
dlclose sous Unix.

En fait, je ne crois pas que tu pourrais jamais être 100% sûr
que le ficher nommé xxx.so (ou xxx.dll sous Windows) est un
plug-in qui te convient. Si en revanche le dlopen marche, et
dlsym trouve le symbole (ou les symboles), il y a de fortes
chances que le fichier, c'est bon. (Sous Windows, évidemment,
les noms des fonctions sont fort différents, mais le principe
reste le même.)

Enfin, deux points où il faut faire attention :

-- Le nom que tu dois passer à dlsym (ou l'équivalent sous
Windows) n'est PAS le nom tel qu'il apparaît dans ton
programme, mais le nom connu dans le fichier objet. Sous
Unix, la solution la plus simple, c'est de n'exporter que
des fonctions « extern "C" » ; pour des fonctions C (ou
«@extern "C" »), le nom est identique. Sous Windows, il y a
aussi quelque chose de spécial à faire, mais je ne sais pas
quoi. (Tout ce que je sais, c'est qu'une expérience rapide
n'a pas marché quand j'ai essayé. Un collègue spécialiste
Windows m'a expliqué pourquoi, et ce qu'il fallait faire,
mais ne travaillant pas sous Windows, je ne l'ai pas
rétenu.)

-- Sous Unix, dlsym ne rend qu'un void*, et il n'y a pas de
conversion standard void* vers pointeur vers fonction.
Certains compilateurs le supportent avec reinterpret_cast
(ou un cast à la C), comme extension, mais selon l'Open
Group, la solution préconcisée (jusqu'à ce que l'interface
soit corrigée, ce qui est prévu, mais sans qu'ils disent
quand), c'est quelque chose du genre :

extern "C" {
PluginFactory (*f)() ;
}

*reinterpret_cast< void** >( &f ) = dlsym( ... ) ;

Open Systems garantit que dans un système « Unix », cette
conversion marche.

Sous Windows, la fonction correspondante renvoie bien un
pointeur à une fonction (mais il se peut que le problème se
présente à l'enverse, si on veut un pointeur aux données).

Une autre possibilité serait de n'exporter qu'un objet usine ;
on évite ainsi (probablement) les problèmes de nommage et la
conversion officiellement non-supportée. (Mais je n'ai pas
essayé cette solution moi-même.)

Ca fait beaucoup de questions...


J'ai pu voir dlopen() et extern "C" (mais pour les librairies en C...).


Pour dlopen(), j'utilise le code suivant :


void* handle = dlopen("./plugins/test.so", RTLD_LAZY);


if (handle == NULL)
{
std::cout << dlerror() << std::endl;
exit(1);
}


En sortie : ./plugins/test.so: Undefined symbol "_ZN8IPluginsC2Ev"


Je crois que Sylvain y a répondu.

Par ailleurs, on m'a dit que dlopen() ne servait pas dans mon
cas...


C'est bien dlopen qui sert sous Unix pour charger les plugins.
Il faudra en revanche bien spécifier l'option RTLD_LOCAL si tu
ne veux pas avoir de problèmes quand tu charges plusieurs
plugins symultanemment.

--
James Kanze
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34

Avatar
Delf
James Kanze wrote:

je ne connais même pas de
solution portable : les fonctions s'appelle dlopen, dlsym et
dlclose sous Unix.


Je ne cherche pas à faire une application portable, qu'elle fonctionne
sous Linux et UNiX, ça ne sera déjà pas mal :)

Pour l'histoire du répertoire, j'ai utilisé la panoplie opendir,
readdir, etc.

-- Sous Unix, dlsym ne rend qu'un void*, et il n'y a pas de
conversion standard void* vers pointeur vers fonction.
Certains compilateurs le supportent avec reinterpret_cast
(ou un cast à la C), comme extension, mais selon l'Open
Group, la solution préconcisée (jusqu'à ce que l'interface
soit corrigée, ce qui est prévu, mais sans qu'ils disent
quand), c'est quelque chose du genre :
[...]


Ne connaissant pas le sujet sous UNiX (je viens du monde dotNET), je
suis parti sur ce HowTo :

http://www.faqs.org/docs/Linux-mini/C++-dlopen.html

Pour l'instant, mon PluginsManager basé sur ce document fonctionne sous
FreeBSD. Je testerai par la suite sous d'autres systèmes Linux/UNiX.

Merci.

--
Delf
Do not use this email in Cc!
Tout bonheur commence par un petit déjeuner tranquille.

Avatar
James Kanze
Delf wrote:
James Kanze wrote:


je ne connais même pas de solution portable : les fonctions
s'appelle dlopen, dlsym et dlclose sous Unix.



Je ne cherche pas à faire une application portable, qu'elle
fonctionne sous Linux et UNiX, ça ne sera déjà pas mal :)


Alors, il vaut mieux poser la question dans un groupe Unix. Ici,
on essaie toujours à être portable. (Aussi, dans la pratique, je
constate que souvent, dès que ça marche sur un système, les
chefs décident qu'il faut que ça marche sur d'autres, bien
qu'ils aient juré au départ que la portabilitè n'était pas
importante.)

Pour l'histoire du répertoire, j'ai utilisé la panoplie opendir,
readdir, etc.


-- Sous Unix, dlsym ne rend qu'un void*, et il n'y a pas de
conversion standard void* vers pointeur vers fonction.
Certains compilateurs le supportent avec reinterpret_cast
(ou un cast à la C), comme extension, mais selon l'Open
Group, la solution préconcisée (jusqu'à ce que l'interface
soit corrigée, ce qui est prévu, mais sans qu'ils disent
quand), c'est quelque chose du genre :
[...]



Ne connaissant pas le sujet sous UNiX (je viens du monde
dotNET), je suis parti sur ce HowTo :


http://www.faqs.org/docs/Linux-mini/C++-dlopen.html


Pour l'instant, mon PluginsManager basé sur ce document
fonctionne sous FreeBSD. Je testerai par la suite sous
d'autres systèmes Linux/UNiX.


Par hazard, peut-être. Le code dans leur exemple est faux, et un
compilateur C++ n'a pas le droit de l'accepter (selon la norme).
(En plus, il utilise un cast à la C, qui est interdit dans
beaucoup de standards de programmation.)

--
James Kanze
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34


1 2 3