【问题标题】:How would a loaded library function call a symbol in the main application?加载的库函数如何在主应用程序中调用符号?
【发布时间】:2008-12-25 01:30:10
【问题描述】:

当通过函数dlopen()打开共享库时,有没有办法在主程序中调用函数?

【问题讨论】:

  • 下面的答案很好地回答了这个问题,但我不得不问——你能解释一下这个要求的更广泛背景吗?当我发现需要这样做时,要么是构建一个可扩展性/插件模型,要么是因为我的程序没有很好地分解。
  • 它也可以用于反转控制,不是吗?在库中定义应用程序的流程,而实际实现在主应用程序中
  • 想想 Perl XS 模块。它需要使用低级 Perl 函数(比如 newSViv() 从整数创建 SV);如果模块使用 Perl 中的 newSViv() 函数,而不是将其自己的副本嵌入到模块的共享对象中,这会很方便。此外,代码需要标准 C 库。

标签: c module dlopen


【解决方案1】:

dlo.c(lib)的代码:

#include <stdio.h>

// function is defined in main program
void callb(void);

void test(void) {
    printf("here, in lib\n");
    callb();
}

编译

gcc -shared -olibdlo.so dlo.c

这里是主程序的代码(从dlopen手册页复制,并调整):

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

void callb(void) {
    printf("here, i'm back\n");
}

int
main(int argc, char **argv)
{
    void *handle;
    void (*test)(void);
    char *error;

    handle = dlopen("libdlo.so", RTLD_LAZY);
    if (!handle) {
        fprintf(stderr, "%s\n", dlerror());
        exit(EXIT_FAILURE);
    }

    dlerror();    /* Clear any existing error */

    *(void **) (&test) = dlsym(handle, "test");

    if ((error = dlerror()) != NULL)  {
        fprintf(stderr, "%s\n", error);
        exit(EXIT_FAILURE);
    }

    (*test)();
    dlclose(handle);
    exit(EXIT_SUCCESS);
}

构建
gcc -ldl -rdynamic main.c

输出:

[js@HOST2 dlopen]$ LD_LIBRARY_PATH=. ./a.out
here, in lib
here, i'm back
[js@HOST2 dlopen]$

-rdynamic 选项将所有符号放入动态符号表(映射到内存)中,而不仅仅是使用符号的名称。进一步了解它here。当然,您也可以提供定义库和主程序之间接口的函数指针(或函数指针的结构)。这实际上是我可能会选择的方法。我从其他人那里听说,在 Windows 中执行 -rdynamic 并不容易,而且它还可以使库和主程序之间的通信更加清晰(您可以精确控制可以调用的内容和不可以调用的内容),但它也需要更多的家务。

【讨论】:

  • 很棒的答案。有人赢得了 10k 代表 :-)
  • 这是一种有趣的选角方式。将 dlsym() 的返回值转换为指向函数的指针,而不是假装您分配的指针与函数 dlsym() 返回的类型相同,这不是更正常吗?
  • C 没有说明如果从 void* 转换为函数指针会发生什么。遗憾的是,我还没有找到说明它是未定义行为的段落,但手册页上说是这样。
  • 您可以在此处找到更多信息:opengroup.org/onlinepubs/009695399/functions/dlsym.html。无论如何,C++ 明确禁止从 void* 或对象指针转换为函数指针类型或返回。
  • 所以像我一样将东西强制转换为 void** 然后取消引用仍然是未定义的行为,但 XSI 兼容系统将支持它,并且 C++ 没有明确禁止(使其格式错误)它(相反,它说这是未定义的行为)。
【解决方案2】:

是的,如果您为您的库提供指向该函数的指针,我确信该库将能够在主程序中运行/执行该函数。

这是一个例子,还没有编译,所以要小心;)

/* in main app */

/* define your function */

int do_it( char arg1, char arg2);

int do_it( char arg1, char arg2){
  /* do it! */
  return 1;
}

/* some where else in main app (init maybe?) provide the pointer */
 LIB_set_do_it(&do_it);
/** END MAIN CODE ***/

/* in LIBRARY */

int (*LIB_do_it_ptr)(char, char) = NULL;

void LIB_set_do_it( int (*do_it_ptr)(char, char) ){
    LIB_do_it_ptr = do_it_ptr;
}

int LIB_do_it(){
  char arg1, arg2;

  /* do something to the args 
  ...
  ... */

  return LIB_do_it_ptr( arg1, arg2);
}

【讨论】:

  • do_it_ptr 接受一个指向需要 3 个字符参数的函数的指针;您为仅采用 2 个 char 参数的函数分配函数指针。几乎不需要 doit() 的 extern 声明。不需要 do_it_ptr;您可以在当前传递 do_it_ptr 的位置按名称传递 do_it。等等!
  • 那是正确的 :) 事实上你也可以摆脱 LIB_get_it() 并只定义一个新的 LIB_do_it( int (*do_it_ptr)(char, char, char) ) { return do_it_ptr( arg1, arg2, arg3) }
【解决方案3】:

@litb 所讨论的dlopen() 函数主要在使用ELF 格式目标文件的系统上提供。它相当强大,可以让你控制加载的库引用的符号是否可以从主程序中得到满足,并且通常会让它们得到满足。并非所有的共享库加载系统都如此灵活 - 请注意是否要移植您的代码。

@hhafez 概述的回调机制现在可以正常工作,因为代码中的问题已经解决。

【讨论】:

    猜你喜欢
    • 2015-08-07
    • 1970-01-01
    • 2017-08-13
    • 2013-06-09
    • 1970-01-01
    • 2021-05-23
    • 2016-10-18
    • 1970-01-01
    • 2013-01-03
    相关资源
    最近更新 更多