如何正确地将dlsym返回的指针分配给函数指针类型的变量?

时间:2016-04-03 10:18:08

标签: c pointers dlopen dlsym

我正在尝试在代码中使用dlopen()dlsym(),并使用gcc进行编译。

这是第一个文件。

/* main.c */

#include <dlfcn.h>

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

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

    return 0;
}

这是第二个文件。

/* foo.c */

#include <stdio.h>

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

以下是我编译和运行代码的方法。

$ 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

如何摆脱警告?

类型转换没有帮助。如果我尝试将dlsym()的返回值输入到函数指针中,我会收到此警告。

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

什么会让编译器相信这段代码没问题?

6 个答案:

答案 0 :(得分:17)

如果您想要迂腐,请不要尝试解析函数的地址。相反,从动态库中导出某种结构:

在图书馆

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

这种技术实际上很常见,不是为了便携性 - POSIX保证函数指针可以转换为void * - 但是因为它允许更多的灵活性。

答案 1 :(得分:8)

这里的问题是指向对象的指针与函数指针巧妙地分开。在 ISO / IEC 9899:201x 论文§6.3.2.3指针中声明:

  
      
  1. 指向void的指针可以转换为指向any的指针   对象类型。指向任何对象类型的指针可以转换为   指向void并再次返回;结果应该等于   原始指针。
  2.   

  
      
  1. 指向一种类型的函数的指针可以转换为指向   另一种类型的功能又回来了;结果应该比较   等于原始指针。如果转换指针用于   调用一个类型与指向不兼容的函数   类型,行为未定义。
  2.   

因此,函数指针与对象指针不同,因此将void *赋值给函数指针总是严格不符合

无论如何,正如我在评论中所说,在99.9999 .... 9999%的情况下,由于 ANNEX J - 可移植性问题,§J.5.7函数指针强制转换,提到的论文指出:

  
      
  1. 指向对象或void的指针可以转换为指向a的指针   函数,允许将数据作为函数调用(6.5.4)。
  2.   
  3. 指向函数的指针可以强制转换为指向对象或指针的指针   void,允许检查或修改函数(例如,   通过调试器)(6.5.4)。
  4.   

现在在实践方面,避免在更多文件中拆分代码的技术是使用编译指示来抑制一小段代码的迂腐警告。
更残酷的形式可能是:

/* 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

可以启动一种更复杂的方法,在一个小函数中隔离违规代码,然后强制进行内联。它更像是化妆而不是有效的解决方案,但会抑制不必要的诊断:

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

答案 2 :(得分:2)

要保留代码的-pedantic选项,同时让部分代码严格遵守,请将该代码分成带有自定义警告选项的单独文件。

因此,创建一个包装dlsym函数的函数并返回一个函数指针。将它放在一个单独的文件中,并在没有-pedantic的情况下编译该文件。

答案 3 :(得分:1)

这使我的代码足够有趣:

*(void**)(&func_ptr) = dlsym(handle, "function_name");

(我在这里http://pubs.opengroup.org/onlinepubs/009695399/functions/dlsym.html找到了)

答案 4 :(得分:0)

您可以使用union,如下所示:

union {
    void *ptr;
    void (*init_google_logging) (char* argv0);
} orig_func;

orig_func.ptr = dlsym (RTLD_NEXT, "_ZN6google17InitGoogleLoggingEPKc");

orig_func.init_google_logging (argv0);

答案 5 :(得分:-3)

仅编译器&#34;尝试帮助&#34;,因此您必须使用两个类型转换:

#include <inttypes.h>

void (*func)() = (void (*)())(intptr_t)dlsym(handle, "func");