Search code examples
cpointersdlopendlsym

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


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?


Solution

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