关于C#:共享库(.so)如何调用在其加载程序中实现的函数?

 2020-05-23 

How can a shared library (.so) call a function that is implemented in its loading program?

我有一个实现的共享库,并且希望.so调用在加载该库的主程序中实现的函数。

假设我有main.c(可执行文件),其中包含:

1
2
void inmain_function(void*);
dlopen("libmy.so");

在my.c(libmy.so的代码)中,我想调用inmain_function

1
inmain_function(NULL);

无论事实inmain_function是在主程序中定义的,共享库如何调用inmain_function

注意:我想从my.c调用main.c中的符号,反之亦然,这是常见用法。


您有两种选择,可以选择:

选项1:从可执行文件中导出所有符号。
这是简单的选项,仅在构建可执行文件时,添加标志-Wl,--export-dynamic。这将使所有函数可用于库调用。

选项2:使用功能列表创建导出符号文件,然后使用-Wl,--dynamic-list=exported.txt。这需要一些维护,但是更准确。

演示:简单的可执行文件和动态加载的库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdio.h>
#include <dlfcn.h>

void exported_callback() /*< Function we want to export */
{
    printf("Hello from callback!\
"
);
}

void unexported_callback() /*< Function we don't want to export */
{
    printf("Hello from unexported callback!\
"
);
}

typedef void (*lib_func)();

int call_library()
{
   void     *handle  = NULL;
   lib_func  func    = NULL;
   handle = dlopen("./libprog.so", RTLD_NOW | RTLD_GLOBAL);
   if (handle == NULL)
   {
       fprintf(stderr,"Unable to open lib: %s\
"
, dlerror());
       return -1;
   }
   func = dlsym(handle,"library_function");

   if (func == NULL) {
       fprintf(stderr,"Unable to get symbol\
"
);
      return -1;
   }

   func();
   return 0;
}

int main(int argc, const char *argv[])
{
    printf("Hello from main!\
"
);
    call_library();
    return 0;
}

库代码(lib.c):

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int exported_callback();

int library_function()
{
    printf("Hello from library!\
"
);
    exported_callback();
    /* unexported_callback(); */ /*< This one will not be exported in the second case */
    return 0;
}

因此,首先构建库(此步骤没有什么不同):

1
 gcc -shared -fPIC lib.c -o libprog.so

现在,用导出的所有符号构建可执行文件:

1
 gcc -Wl,--export-dynamic main.c -o prog.exe -ldl

运行示例:

1
2
3
4
 $ ./prog.exe
 Hello from main!
 Hello from library!
 Hello from callback!

导出的符号:

1
2
3
 $ objdump -e prog.exe -T | grep callback
 00000000004009f4 g    DF .text  0000000000000015  Base        exported_callback
 0000000000400a09 g    DF .text  0000000000000015  Base        unexported_callback

现在带有导出的列表(exported.txt):

1
2
3
4
5
6
{
    extern"C"
    {
       exported_callback;
    };
};

构建并检查可见符号:

1
2
3
$ gcc -Wl,--dynamic-list=./exported.txt main.c -o prog.exe -ldl
$ objdump -e prog.exe -T | grep callback
0000000000400774 g    DF .text  0000000000000015  Base        exported_callback


您需要在.so中创建一个注册函数,以便可执行文件可以提供指向.so的函数指针,以供以后使用。

像这样:

1
2
3
4
5
6
7
8
9
10
void in_main_func () {
// this is the function that need to be called from a .so
}

void (*register_function)(void(*)());
void *handle = dlopen("libmylib.so");

register_function = dlsym(handle,"register_function");

register_function(in_main_func);

register_function需要将函数指针存储在.so中的变量中,.so中的其他函数才能找到它。

您的mylib.c需要看起来像这样:

1
2
3
4
5
6
7
8
9
10
11
void (*callback)() = NULL;

void register_function( void (*in_main_func)())
{
    callback = in_main_func();
}

void function_needing_callback()
{
     callback();
}


  • 将主函数的原型放在.h文件中,并将其包含在主库代码和动态库代码中。

  • 使用GCC,只需使用-rdynamic标志编译主程序。

  • 加载后,您的库将能够从主程序中调用该函数。

  • 进一步的解释是,编译后,动态库中的主代码中的函数将具有未定义的符号。在您的主应用程序加载库后,该符号将由主程序的符号表解析。我已经多次使用上述模式,并且它就像一种魅力。


    以下代码可用于在代码中加载动态库(以防万一有人看了看怎么做后来到这里):

    1
    2
    3
    4
    5
    6
    7
    void* func_handle = dlopen ("my.so", RTLD_LAZY); /* open a handle to your library */

    void (*ptr)() = dlsym (func_handle,"my_function"); /* get the address of the function you want to call */

    ptr(); /* call it */

    dlclose (func_handle); /* close the handle */

    不要忘记放置#include 并与–ldl选项链接。

    您可能还想添加一些逻辑,以检查是否返回了NULL。如果是这种情况,您可以致电dlerror,它应该会给您一些有意义的消息来描述问题。

    但是,其他海报为您的问题提供了更合适的答案。