How to correctly assign a pointer returned by dlsym into a variable of function pointer type?

Lone Learner picture Lone Learner · Apr 3, 2016 · Viewed 7.7k times · Source

I am trying to use dlopen() and dlsym() in my code and compile it with gcc.

Here is the first file.

/* main.c */

#include <dlfcn.h>

int main()
{
    void *handle = dlopen("./foo.so", RTLD_NOW);

    if (handle) {
        void (*func)() = dlsym(handle, "func");
        func();
    }

    return 0;
}

Here is the second file.

/* foo.c */

#include <stdio.h>

void func()
{
    printf("hello, world\n");
}

Here is how I compile and run the 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

How can I get rid of the warning?

Type casting doesn't help. If I try to type cast the return value of dlsym() into a function pointer, I get this warning instead.

main.c:10:26: warning: ISO C forbids conversion of object pointer to function pointer type [-Wpedantic]
         void (*func)() = (void (*)()) dlsym(handle, "func");
                          ^

What would convince the compiler that this code is fine?

Answer

rici picture rici · Apr 3, 2016

If you want to be pedantically correct, don't try to resolve the address of a function. Instead, export some kind of structure from the dynamic library:

In the library

struct export_vtable {
   void (*helloworld)(void);
};
struct export_vtable exports = { func };

In the caller

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;
}

This technique is actually quite common, not for portability -- POSIX guarantees that function pointers can be converted to and from void* -- but because it allows more flexibility.