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
James Kanze wrote:

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.)


Ah ben là je suis tout démoralisé...

--
Delf
Do not use this email in Cc!
Tant que l'homme sera mortel, il ne sera jamais décontracté.

Avatar
Sylvain
James Kanze wrote on 14/05/2006 17:17:
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. [...]


un peu extrémiste là ?!

la question est bien comment définir un modèle abstrait de plug-in
instancié depuis des libs chargées dynamiquement - c'est bien une
question de langage et de schéma supporté par ce langage. (les thèmes
concourants comme le 'mangling' des symboles ou le nécessaire recours à
des fonctions C est intéressant ici aussi).

même s'il faudra, certes, au final utiliser une API unix ou win32.

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.)


j'ai survolé le doc sans y voir ce qui est faux (ton propos aurait pu
utilement détailler l'erreur éventuelle).

au delà, un cast C est en effet appliqué sur les fonctions exportées par
la lib., mais quelle serait l'autre façon ?? sauf erreur un cast
dynamique n'est possible (et/ou utile) que si il existe des informations
RTTI sur la chose castée - dès lors cette chose a des RTTI s'il s'agit
d'une instance, mais pas s'il s'agit d'une fonction statique - j'ai omis
quelque chose ?

Sylvain.



Avatar
kanze
Sylvain wrote:
James Kanze wrote on 14/05/2006 17:17:

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.)


j'ai survolé le doc sans y voir ce qui est faux (ton propos
aurait pu utilement détailler l'erreur éventuelle).


Ils invoquent une conversion d'un void* en pointeur à fonction,
ce que supporte ni le C, ni le C++. Dans les deux cas, un
compilateur « conforme » est obligé à émettre une diagnostique.

Les spécifications de Unix montre bien la solution préconcisée.
Qui dépend bien d'un comportement spécifique à l'implémentation,
mais qui n'exige pas de message d'erreur du compilateur, et qui
est garanti à marcher sur toute implémentation qui se dit Unix.
(Et qui marche aussi sous Linux, même si Linux n'est pas tout à
fait Unix.)

au delà, un cast C est en effet appliqué sur les fonctions
exportées par la lib., mais quelle serait l'autre façon ??


Il faut bien une conversion, certes. Mais de préférence une qui
est légale : la « Single Unix Specification »
(http://www.unix.org/single_unix_specification/) dit :
*(void **)(&fptr) = dlsym(handle, "my_function");
Ce qui en C++ donne :
*reinterpret_cast< void** >( &fptr ) = dlsym(handle, "my_function"
) ;

Il est fréquent que les compilateurs sous Unix permettent aussi
le cast directement sur le résultat de dlsym ; c'est une
extension, qui risque de ne pas marcher si on démande un maximum
de conformité. Et évidemment, cette extension aussi se mappe à
un reinterpret_cast en C++ -- c'est le cas avec Sun CC, par
exemple. (Mais pas g++ -- pour des raisons qui me dépassent, gcc
supporte le cast à la C, mais g++ ne supporte pas le
reinterpret_cast.) Mais le fait que certains compilateurs le
permettent ne le rend pas correct -- et tout compilateur C++ est
obligé à accepter la solution correct. (Question : est-ce que le
code sur la page que tu cites marche avec Comeau C++, en mode
strict ?)

sauf erreur un cast dynamique n'est possible (et/ou utile) que
si il existe des informations RTTI sur la chose castée - dès
lors cette chose a des RTTI s'il s'agit d'une instance, mais
pas s'il s'agit d'une fonction statique - j'ai omis quelque
chose ?


Un cast dynamique n'est effectivement pas possible ici. Que
l'objet réel soit polymorphique ou non -- c'est le type statique
qui doit être polymorphique pour qu'il marche, et void* n'est
pas polymorphique. Aussi, un cast dynamique ne permet pas de
passer d'un pointeur à un objet à un pointeur à une fonction --
en fait, une telle conversion n'est pas possible en C++ (ni en
C++).

--
James Kanze GABI Software
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
Gabriel Dos Reis
"kanze" writes:

| Sylvain wrote:
| > James Kanze wrote on 14/05/2006 17:17:
|
| > > > 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.)
|
| > j'ai survolé le doc sans y voir ce qui est faux (ton propos
| > aurait pu utilement détailler l'erreur éventuelle).
|
| Ils invoquent une conversion d'un void* en pointeur à fonction,
| ce que supporte ni le C, ni le C++.

il sera permis dans C++, de « manière conditionnelle. »

[ La notion de « conditionally supported » a entraîné un débat
chauffé, surréaliste à la réunion de Lillehammer l'année dernière. ]


[...]

| exemple. (Mais pas g++ -- pour des raisons qui me dépassent, gcc
| supporte le cast à la C, mais g++ ne supporte pas le
| reinterpret_cast.)

Alors, tu as trouvé un bug dans le compilateur.

-- Gaby
Avatar
kanze
Gabriel Dos Reis wrote:
"kanze" writes:

| Sylvain wrote:
| > James Kanze wrote on 14/05/2006 17:17:

| > > > 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.)

| > j'ai survolé le doc sans y voir ce qui est faux (ton propos
| > aurait pu utilement détailler l'erreur éventuelle).

| Ils invoquent une conversion d'un void* en pointeur à fonction,
| ce que supporte ni le C, ni le C++.

il sera permis dans C++, de « manière conditionnelle. »


Ce qui est probablement la solution la moins mauvaise.

[ La notion de « conditionally supported » a entraîné un débat
chauffé, surréaliste à la réunion de Lillehammer l'année
dernière. ]


Mais ce n'est pas le premier cas d'une opération qui est
« conditionally supported », non. Si je peux convertir un
pointeur en entier (et quel entier), par exemple.

[...]

| exemple. (Mais pas g++ -- pour des raisons qui me dépassent, gcc
| supporte le cast à la C, mais g++ ne supporte pas le
| reinterpret_cast.)

Alors, tu as trouvé un bug dans le compilateur.


Moi, je le considèrerais comme tel, mais j'avais l'impression
que c'était plus ou moins voulu. Et je ne l'estîme pas assez
important pour ouvrir une polémique. Mais je ne trouve pas
« normal » que :

extern void* f() ;

typedef void (*PF)() ;
PF pf1 = (PF)f( ... ) ; // marche
PF pf2 = reinterpret_cast< PF >( f( ... ) ) ;
// erreur de compil.

À mon avis : pour des raisons historiques -- et de compatibilité
avec les autres compilateurs sur les plateformes Unix --, le
compilateur pourrait accepter les deux par défaut, mais réfuser
les deux quand l'option -std=c++98 (ou -stdÉ0, ou simplement
-ansi) est présente. En ce qui concerne le comportement par
défaut, je suis assez ouvert ; je crois que du point de vue
pratique, il faut bien une mode qui les accepte, ne serait-ce
que pour aider les gens qui portent du code développé
originalement avec le compilateur natif. Mais quand je démande
la norme (ce que je fais systèmatiquement), je m'attendrais à ce
que le compilateur rejette les deux, parce que la norme -- aussi
bien C que C++ -- les rejette. (Mais si la norme va changer, ce
n'est pas la peine de commencer maintenant à les rejeter, pour
les permettre de nouveau dans trois ou quatre ans.)

--
James Kanze GABI Software
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
Vincent Lascaux
*(void **)(&fptr) = dlsym(handle, "my_function");
Ce qui en C++ donne :
*reinterpret_cast< void** >( &fptr ) = dlsym(handle, "my_function"
) ;


Voilà le texte tiré du site :
The ISO C standard does not require that pointers to functions can be cast
back and forth to pointers to data. Indeed, the ISO C standard does not
require that an object of type void * can hold a pointer to a function.
Implementations supporting the XSI extension, however, do require that an
object of type void * can hold a pointer to a function. The result of
converting a pointer to a function into a pointer to another data type
(except void *) is still undefined, however.

Je ne comprends pas pourquoi le cast de &fptr (adresse d'un pointeur de
fonction) en un void** (adresse d'un pointeur de données) est alors valide.
Je suppose que l'interet d'interdire le cast direct, c'est de permettre à
l'implémentation d'avoir des représentations différentes pour des pointeurs
de fonctions et de données (disons par exemple 4 octets pour les pointeurs
de données et 8 pour les pointeurs de fonction). Dans ce cas, le code
précédent ne fonctionnerait pas non plus. *(void**)(&fptr) me semble une
façon compliquée d'écrire (void*)fptr, et je ne vois pas l'interet
d'autoriser l'un et pas l'autre.

--
Vincent

Avatar
Gabriel Dos Reis
"kanze" writes:

[...]

| > [ La notion de « conditionally supported » a entraîné un débat
| > chauffé, surréaliste à la réunion de Lillehammer l'année
| > dernière. ]
|
| Mais ce n'est pas le premier cas d'une opération qui est
| « conditionally supported », non. Si je peux convertir un
| pointeur en entier (et quel entier), par exemple.

Cela n'est pas plutôt « implementation defined » ?

La notion de « conditionally supported » a été inventée à la dernière
réunion de Lillehammer.

| > [...]
|
| > | exemple. (Mais pas g++ -- pour des raisons qui me dépassent, gcc
| > | supporte le cast à la C, mais g++ ne supporte pas le
| > | reinterpret_cast.)
|
| > Alors, tu as trouvé un bug dans le compilateur.
|
| Moi, je le considèrerais comme tel, mais j'avais l'impression
| que c'était plus ou moins voulu.

Un cast « C » est un static_cast, const_cast (ou const_cast suivi de
static_cast) ou un reintepret_cast. Donc, ou bien c'est accepté dans
les deux cas, ou rejeté dans les deux cas.

-- Gaby
Avatar
kanze
Gabriel Dos Reis wrote:
"kanze" writes:

[...]

| > [ La notion de « conditionally supported » a entraîné un dé bat
| > chauffé, surréaliste à la réunion de Lillehammer l'année
| > dernière. ]

| Mais ce n'est pas le premier cas d'une opération qui est
| « conditionally supported », non. Si je peux convertir un
| pointeur en entier (et quel entier), par exemple.

Cela n'est pas plutôt « implementation defined » ?


C'est « implementation defined » si la conversion est supportée
ou non.

La notion de « conditionally supported » a été inventée à la
dernière réunion de Lillehammer.


Mais où est la différence ? Qu'une implémentation n'est pas
obligée à documenter qu'elle le supporte ou non ? Que les
conditions qui déterminent si elle le supporte ou non sont
définie par la norme ? Mais c'est déjà le cas pour les
conversions pointeur de/vers entier : « A pointer can be
explicitly converted to any integral type large enough to hold
it. » C-à-d que si un tel type existe, l'implémentation est
obligée à supporter la conversion, et si il n'existe pas, elle
n'a pas le droit de le supporter.

| > [...]

| > | exemple. (Mais pas g++ -- pour des raisons qui me dépassent, gcc
| > | supporte le cast à la C, mais g++ ne supporte pas le
| > | reinterpret_cast.)

| > Alors, tu as trouvé un bug dans le compilateur.

| Moi, je le considèrerais comme tel, mais j'avais l'impression
| que c'était plus ou moins voulu.

Un cast « C » est un static_cast, const_cast (ou const_cast suivi de
static_cast) ou un reintepret_cast. Donc, ou bien c'est accepté dans
les deux cas, ou rejeté dans les deux cas.


C'est mon avis, mais ce n'est pas l'avis du compilateur g++:-).
Ou plutôt, ça ne l'était pas -- je viens d'essayer avec 4.1.0
(plutôt qu'avec le 3.4.0 ou le 3.4.3 dont on se sert d'habitude
ici), et il accepte mantenant les deux.

Il serait toujours préférrable à mon avis qu'il désactive
l'extension quand je précise « -stdÉ8 -pedantic », mais ce
n'est pas la fin du monde.

--
James Kanze GABI Software
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
kanze
Vincent Lascaux wrote:
*(void **)(&fptr) = dlsym(handle, "my_function");
Ce qui en C++ donne :
*reinterpret_cast< void** >( &fptr ) = dlsym(handle, "my_function"
) ;


Voilà le texte tiré du site :
The ISO C standard does not require that pointers to
functions can be cast back and forth to pointers to data.
Indeed, the ISO C standard does not require that an object of
type void * can hold a pointer to a function.
Implementations supporting the XSI extension, however, do
require that an object of type void * can hold a pointer to a
function. The result of converting a pointer to a function
into a pointer to another data type (except void *) is still
undefined, however.

Je ne comprends pas pourquoi le cast de &fptr (adresse d'un
pointeur de fonction) en un void** (adresse d'un pointeur de
données) est alors valide.


C'est une conversion entre des pointeurs de données : un
pointeur, même vers une fonction, est un objet (au sens du C++),
et non une fonction.

Note bien que la norme C++ ne dit pas que la conversion est
« valide », au sens le plus fort du mot. Elle dit qu'elle est
valide syntactiquement, que le résultat dépend de
l'implémentation, et que c'est un comportement indéfini si tu
accèdes à l'objet à travers le pointeur converti. L'importance
ici, c'est 1) qu'elle est valide syntactiquement, donc, pas
d'erreur du compilateur ; et 2) qu'elle a un comportement
indéfini, donc l'implémentation a le droit de le définir.

Je suppose que l'interet d'interdire le cast direct, c'est de
permettre à l'implémentation d'avoir des représentations
différentes pour des pointeurs de fonctions et de données
(disons par exemple 4 octets pour les pointeurs de données et
8 pour les pointeurs de fonction).


Tout à fait.

Dans ce cas, le code précédent ne fonctionnerait pas non plus.
*(void**)(&fptr) me semble une façon compliquée d'écrire
(void*)fptr, et je ne vois pas l'interet d'autoriser l'un et
pas l'autre.


La différence, ce n'est pas que la norme C++ autorise un, et non
l'autre. La différence, c'est que la norme C++ dit qu'un est une
erreur de syntaxe, et que l'autre a un comportement indéfini. En
ce qui concerne la norme, les deux sont faux. Dans le cas de la
conversion directe, c'est trivial de reconnaître ; donc, on
exige une erreur du compilateur. Dans le cas de la conversion
acceptée, c'est que c'est très difficile, sinon impossible, pour
le compilateur de detecter tous les cas de violation.

Si je voulais rester dans l'esprit de la norme C++ (et C), la
seule façon de faire la conversion serait d'écrire quelque chose
comme :

void* tmp = dlsym( handle, "my_function" ) ;
void (*pf)() ;
mempcy( &pf, &tmp, sizeof( pf ) ) ;

Ici, pas de comportement indéfini selon la norme (à condition
que les deux pointeurs aient la même taille), mais quand même un
comportement indéfini quand tu essaies à déréferencier pf : ce
que la norme garantie ici, c'est qu'après le memcpy, pf aura le
même image, en terme de bits, que tmp. Mais rien ne garantit
que les bits dans un void* valide donne un pointeur à fonction
valide. (On pourrait, par exemple, imaginer une architecture où
un pointeur à données a toujours le bit de poids fort 0, et un
pointeur à fonction le bit de poids fort 1. Et que ce soit
contrôler par le hardware dès qu'on mettait le pointeur dans un
régistre.)

Les deux dernières phrases que tu cites manque toujours de
précision, en revanche. D'abord, le résultat de la conversion
d'un pointeur à une fonction a un pointeur à un type de données
(objet, dans le vocabulaire de la norme C++) n'est pas
indéfini ; la conversion n'existe carrément pas en C ou en C++.
Et un essai de l'écrire est une violation des contraintes
sémantiques (en langage de la norme C), et exige une
diagnostique. Un compilateur qui ne râle pas quand tu écris
(void (*)())dlsym(...) n'est pas conforme. Deuxièmement, ce qui
est exigé par l'extenion XSI, ce n'est pas seulement que les
pointeurs aux données et les pointeurs aux fonctions aient la
meme taille. Il faut que leur représentation soit la même aussi.

Et si tout ça ne semble pas très logique, il y a une raison.
C'est la semblance ne trompe pas, et qu'il n'est pas très
logique. C'est plutôt un hack après les faits d'essayer à faire
co-exister les exigeances contradictoires. Que ce soit légal ou
non, les compilateurs C sous Unix ont, en général, accepter les
conversions entre void* et des pointeurs à des fonctions. Et les
gens qui ont spécifié dlsym au début prenaient comme référence
leur compilateur, et non la norme C. (En fait, dlsym est aparu
avant qu'il y a eu une norme C.) Ensuite, on a une pratique
existante, que Open Systems a voulu « normaliser ». La solutoin
adoptée est un peu tordue, mais 1) elle normalisait bien
l'interface présente dans les Unix existant, et 2) elle
n'exigeait pas la non-conformité avec la norme C aux
compilateurs.

Note aussi la dernière phrase dans la « Rationale » : « Due to
the problem noted here, a future version may either add a new
function to return function pointers, or the current interface
may be deprecated in favor of two new functions: one that
returns data pointers and the other that returns function
pointers. » Les auteurs de la spécification Open System ne sont
pas vraiment contents de la situation non plus.

(En C++, il existe une solution simple : déclarer un objet
fonctionnel, plutôt qu'une fonction. Du coup, la conversion ne
pose plus de problèmes, et normalement, on a aussi contourné
l'histoire de mangling. Seulement... on a alors un problème sous
Windows, où la situation est l'inverse : il existe une requête
pour obtenir le nom d'une fonction, mais non une pour obtenir le
nom d'un objet:-).)

--
James Kanze GABI Software
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
Gabriel Dos Reis
"kanze" writes:

| Gabriel Dos Reis wrote:
| > "kanze" writes:
|
| > [...]
|
| > | > [ La notion de « conditionally supported » a entraîné un débat
| > | > chauffé, surréaliste à la réunion de Lillehammer l'année
| > | > dernière. ]
|
| > | Mais ce n'est pas le premier cas d'une opération qui est
| > | « conditionally supported », non. Si je peux convertir un
| > | pointeur en entier (et quel entier), par exemple.
|
| > Cela n'est pas plutôt « implementation defined » ?
|
| C'est « implementation defined » si la conversion est supportée
| ou non.

C'est différent.

| > La notion de « conditionally supported » a été inventée à la
| > dernière réunion de Lillehammer.
|
| Mais où est la différence ? Qu'une implémentation n'est pas
| obligée à documenter qu'elle le supporte ou non ?

L'implémentation est obligée de documenter. Certains voulaient même un
« feature test ». Du moins, c'est l'un des volets qui a nourri le
débat. C'est subtilement assez différent de « implementation defined »

-- Gaby
1 2 3