一个共享库(.so)如何调用在其加载器代码中实现的函数?

27

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

假设我有一个包含以下内容的可执行文件main.c:

void inmain_function(void*);
dlopen("libmy.so");
在我的.c文件(libmy.so的代码)中,我想调用inmain_function函数:
inmain_function(NULL);

共享库怎样调用主程序中定义的inmain_function函数,注意我希望从my.c中调用main.c中的函数,而不是相反(这是常见的用法)。

4个回答

48
您有两个选择,可以从中选择一个:
选项 1:从可执行文件中导出所有符号。这是简单的选项,只需在构建可执行文件时添加一个标志-Wl,--export-dynamic。这将使所有函数都可用于库调用。
选项 2:创建一个导出符号文件,并使用-Wl,--dynamic-list=exported.txt。这需要一些维护,但更加准确。
演示:简单的可执行文件和动态加载库。
#include <stdio.h>
#include <dlfcn.h>

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

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

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\n", dlerror());
       return -1;
   }
   func = dlsym(handle, "library_function");

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

   func();
   return 0;
}

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

库代码(lib.c):

#include <stdio.h>
int exported_callback();

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

首先,构建库(此步骤无差别):

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

现在使用导出所有符号的方式构建可执行文件:
 gcc -Wl,--export-dynamic main.c -o prog.exe -ldl

运行示例:

 $ ./prog.exe
 Hello from main!
 Hello from library!
 Hello from callback!

导出的符号:
 $ 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):
{
    extern "C"
    {
       exported_callback;
    };
};

构建和检查可见符号:

$ 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

当您像这样编译libprog.so时,它会发现exported_callback是一个缺失的符号。 - kritzikratzi
2
回答我的问题:可以禁用“未定义的符号”错误,例如在clang中使用-undefined dynamic_lookup。在Linux上使用gcc时,这会像帖子中描述的那样神奇地工作,但我不知道为什么。 - kritzikratzi
2
这个答案比被接受的那个好多了!我希望它能排在最前面。 - SergeyA

17

您需要在您的.so文件中编写一个注册函数,以便可执行文件可以将一个函数指针传递给您的.so文件以供后续使用。

就像这样:

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文件需要类似于以下内容:

void (*callback)() = NULL;

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

void function_needing_callback() 
{
     callback();
}

2
我宁愿使用if (callback) { callback(); return 0; } else { return -1; }来表示错误并避免调用NULL(这将是致命的)。 - glglgl
好的,已删除第一行。 - user746527

6
  1. 将主函数的原型放在.h文件中,并在主程序和动态库代码中都包含它。

  2. 使用GCC编译主程序时,只需使用-rdynamic标志即可。

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

稍微解释一下,一旦编译完成,您的动态库将在其中具有一个未定义的符号,用于表示主代码中的函数。在主应用程序加载库时,该符号将由主程序的符号表解析。我已经多次使用上述模式,非常有效。


1
好奇一下,如果另一个共享库也有相同的符号会发生什么? - user746527
主应用程序的符号将覆盖库的符号,除非使用“static”关键字定义了库函数。 - mshildt

0

以下代码可用于加载动态库并从加载调用中调用它(如果有人在寻找如何在.so库中加载和调用函数,则可以使用以下代码)

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 <dlfcn.h>并链接–ldl选项。

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

其他帖子提供了更适合您问题的答案。


是的,这将从另一个文件(例如您的主文件)调用您的.so库中的函数。这是您所指的吗?抱歉? - Nobilis
因为其他人提供的解决方案比我更好,所以我不确定是否需要添加任何内容。如果您坚持,我可以删除它(尽管在某些人寻找加载动态库的方法时仍然可能有用)。 - Nobilis
4
这篇帖子实际上没有回答原帖提出的问题。 - Translunar
@Dr.JohnnyMohawk 那是非常明显的,通过评论可以看出来。 - Nobilis
当然。只需遵循 StackOverflow 提供的说明——也就是说,解释你的投票理由。 - Translunar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接