J'essaie d'utiliser dlopen()
et dlsym()
dans mon code et de le compiler avec gcc
.
Voici le premier fichier.
/* main.c */
#include <dlfcn.h>
int main()
{
void *handle = dlopen("./foo.so", RTLD_NOW);
if (handle) {
void (*func)() = dlsym(handle, "func");
func();
}
return 0;
}
Voici le deuxième fichier.
/* foo.c */
#include <stdio.h>
void func()
{
printf("hello, world\n");
}
Voici comment je compile et exécute le code.
$ gcc -std=c99 -pedantic -Wall -Wextra -shared -fPIC -o foo.so foo.c
$ gcc -std=c99 -pedantic -Wall -Wextra -ldl -o main main.c
main.c: In function ‘main’:
main.c:10:26: warning: ISO C forbids initialization between function pointer and ‘void *’ [-Wpedantic]
void (*func)() = dlsym(handle, "func");
^
$ ./main
hello, world
Comment puis-je me débarrasser de l'avertissement?
Le typage n'aide pas. Si j'essaie de transtyper la valeur de retour de dlsym()
dans un pointeur de fonction, j'obtiens plutôt cet avertissement.
main.c:10:26: warning: ISO C forbids conversion of object pointer to function pointer type [-Wpedantic]
void (*func)() = (void (*)()) dlsym(handle, "func");
^
Qu'est-ce qui convaincrait le compilateur que ce code convient?
Si vous voulez être correct sur le plan pédagogique, n'essayez pas de résoudre l'adresse d'une fonction. Exportez plutôt une sorte de structure à partir de la bibliothèque dynamique:
struct export_vtable {
void (*helloworld)(void);
};
struct export_vtable exports = { func };
struct export_vtable {
void (*helloworld)(void);
};
int main() {
struct export_vtable* imports;
void *handle = dlopen("./foo.so", RTLD_NOW);
if (handle) {
imports = dlsym(handle, "exports");
if (imports) imports->helloworld();
}
return 0;
}
Cette technique est en fait assez courante, pas pour la portabilité - POSIX garantit que les pointeurs de fonction peuvent être convertis vers et depuis void * - mais parce qu'elle permet plus de flexibilité.
Le problème ici est qu'un pointeur sur un objet est séparé subtilement d'un pointeur de fonction. Dans ISO/IEC 9899: 201x paper §6.3.2.3 Pointeurs c'est écrit:
- Un pointeur à annuler peut être converti en ou à partir d'un pointeur vers tout type d'objet . Un pointeur sur n'importe quel type d'objet peut être converti en un pointeur À annuler et inverser; le résultat doit être égal à le pointeur d'origine.
.
- Un pointeur sur une fonction d'un type peut être converti en un pointeur sur Une fonction d'un autre type et inversement; le résultat doit comparer égal au pointeur d'origine. Si un pointeur converti est utilisé pour Appeler une fonction dont le type n'est pas compatible avec le type pointé vers , Le comportement n'est pas défini.
Ainsi, un pointeur de fonction est différent des pointeurs d’objets et, par conséquent, l’affectation d’un void *
à un pointeur de fonction est toujours strictement non conforme .
Quoi qu'il en soit, comme je l'ai dit dans des commentaires, dans 99,9999 ... dans 9999% des cas, cela est autorisé grâce à la ANNEXE J - Problèmes de portabilité, §J.5.7 Pointeur de fonction jeté du document précédemment mentionné qui indique:
- Un pointeur sur un objet ou sur un objet peut être converti en un pointeur vers une fonction , Permettant ainsi à des données d'être appelées en tant que fonction (6.5.4).
- Un pointeur sur une fonction peut être converti en un pointeur sur un objet ou sur Vide, permettant à une fonction d'être inspectée ou modifiée (par exemple, Par un débogueur) (6.5.4).
Sur le plan pratique, une technique qui évite le fractionnement du code en davantage de fichiers consiste à utiliser des pragmas pour supprimer les avertissements pédants pour un petit morceau de code.
La forme la plus brutale peut être:
/* main.c */
#include <dlfcn.h>
#pragma GCC diagnostic Push //Save actual diagnostics state
#pragma GCC diagnostic ignored "-pedantic" //Disable pedantic
int main()
{
void *handle = dlopen("./foo.so", RTLD_NOW);
if (handle) {
void (*func)() = dlsym(handle, "func");
func();
}
return 0;
}
#pragma GCC diagnostic pop //Restore diagnostics state
Un moyen plus sophistiqué pourrait être activé en isolant le code incriminé dans une petite fonction, puis en forçant son inline. C'est plus un maquillage qu'une solution efficace, mais supprimera le diagnostic non désiré:
/* main.c */
#include <dlfcn.h>
#pragma GCC diagnostic Push //Save actual diagnostics state
#pragma GCC diagnostic ignored "-pedantic" //Disable pedantic
void (*)() __attribute__((always_inline)) Assigndlsym(void *handle, char *func)
{
return dlsym(handle, func); //The non compliant assignment is done here
}
#pragma GCC diagnostic pop //Restore diagnostics state
int main()
{
void *handle = dlopen("./foo.so", RTLD_NOW);
if (handle) {
void (*func)() = Assigndlsym(handle, "func"); //Now the assignment is compliant
func();
}
return 0;
}
Pour conserver l'option -pedantic
pour votre code tout en ayant des parties de code non strictement conformes, séparez ce code dans un fichier séparé avec des options d'avertissement personnalisées.
Donc, créez une fonction qui enveloppe la fonction dlsym
et retourne un pointeur de fonction. Placez-le dans un fichier séparé et compilez-le sans -pedantic
.
Cela a rendu mon code suffisamment pédant:
*(void**)(&func_ptr) = dlsym(handle, "function_name");
(Je l'ai trouvé ici http://pubs.opengroup.org/onlinepubs/009695399/functions/dlsym.html )
vous pouvez utiliser union
, comme ceci:
union {
void *ptr;
void (*init_google_logging) (char* argv0);
} orig_func;
orig_func.ptr = dlsym (RTLD_NEXT, "_ZN6google17InitGoogleLoggingEPKc");
orig_func.init_google_logging (argv0);