5

我正在尝试执行以下操作

  enum types {None, Bool, Short, Char, Integer, Double, Long, Ptr};
  int main(int argc, char ** args) {
     enum types params[10] = {0};
     void* triangle = dlopen("./foo.so", RTLD_LAZY);
     void * fun = dlsym(triangle, ars[1]);

     <<pseudo code>>
  }

伪代码类似于

fun = {}
for param in params:
      if param == None:
         fun += void
      if param == Bool:
          fun += Boolean
      if param == Integer:
          fun += int
      ...
 returnVal = fun.pop()
 funSignature = returnval + " " + funName + "(" + Riffle(fun, ",") + ")"
 exec funSignature

谢谢

4

3 回答 3

23

实际上,您几乎可以做任何您想做的事情。在 C 语言中(例如,与 C++ 不同),共享对象中的函数仅通过它们的名称来引用。所以,要找到——最重要的是,调用——正确的函数,你不需要它的完整签名。你只需要它的名字!这既是优势也是劣势——但这就是您选择的语言的本质。

让我演示一下,它是如何工作的。

#include <dlfcn.h>

typedef void* (*arbitrary)();
// do not mix this with   typedef void* (*arbitrary)(void); !!!

int main()
{
    arbitrary my_function;
    // Introduce already loaded functions to runtime linker's space
    void* handle = dlopen(0,RTLD_NOW|RTLD_GLOBAL);
    // Load the function to our pointer, which doesn't know how many arguments there sould be
    *(void**)(&my_function) = dlsym(handle,"something");
    // Call something via my_function
    (void)  my_function("I accept a string and an integer!\n",(int)(2*2));
    return 0;
}

事实上,你可以这样调用任何函数。但是,有一个缺点。您实际上需要在编译时知道函数的返回类型。默认情况下,如果您在该 typedef 中省略 void*,则假定 int 为返回类型——是的,它是正确的 C 代码。问题是编译器需要知道返回类型的大小才能正确操作堆栈。

您可以通过技巧来解决它,例如,通过预先声明具有不同大小的返回类型的几种函数类型,然后选择您实际要调用的一种。但更简单的解决方案是要求插件中的函数始终返回 void* 或 int;通过作为参数给出的指针返回的实际结果。

您必须确保始终使用它应该接受的参数的确切数量和类型来调用该函数。密切注意不同整数类型之间的差异(您最好的选择是显式地将参数转换为它们)。

一些评论者报告说,上面的代码不能保证适用于可变参数函数(例如printf)。

于 2009-08-30T19:12:52.660 回答
19

返回的通常是dlsym()一个函数指针——伪装成void *. (如果你向它询问全局变量的名称,它也会返回一个指向该全局变量的指针。)

然后,您可以像使用任何其他指向函数的指针一样调用该函数:

int (*fun)(int, char *) = (int (*)(int, char *))dlsym(triangle, "function");

(*fun)(1, "abc");    # Old school - pre-C89 standard, but explicit
fun(1, "abc");       # New school - C89/C99 standard, but implicit

我是老派;我更喜欢显式表示法,以便读者知道“f​​un”是指向函数的指针,而无需查看其声明。使用新的学校符号,您必须记住fun在尝试查找名为“ ”的函数之前查找变量“ fun()”。

请注意,您不能像您正在做的那样动态地构建函数调用 - 或者,一般情况下不会。要做到这一点需要做更多的工作。您必须提前知道函数指针在参数方式中期望什么,它返回什么以及如何解释它。

管理更多动态函数调用的系统,例如 Perl,对如何调用函数和传递参数有特殊规则,并且不调用(可以说不能调用)具有任意签名的函数。他们只能调用具有预先知道的签名的函数。一种机制(Perl 不使用)是将参数压入堆栈,然后调用知道如何从堆栈中收集值的函数。但是,即使被调用函数操作了这些值,然后调用了任意其他函数,该被调用函数也为任意其他函数提供了正确的调用序列。

C 中的反射很难 - 非常难。它是不可撤销的——但它需要基础设施来支持它并有纪律地使用它,而且它只能调用支持基础设施规则的函数。​​​​​</p>

于 2009-08-30T18:40:10.587 回答
0

正确的解决方案

假设您正在编写共享库;我发现这个问题的最佳解决方案是严格定义和控制哪些函数通过以下方式动态链接:

  1. 设置所有符号隐藏
    • 例如clang -dynamiclib Person.c -fvisibility=hidden -o libPerson.dylib 使用 clang 编译时
  2. 然后使用__attribute__((visibility("default")))andextern "C"选择性地取消隐藏和包含功能
  3. 利润!你知道函数的签名是什么。你写的!

我在Apple 的 Dynamic Library Design Guidelines中找到了这一点。这些文档还包括上述问题的其他解决方案,这只是我最喜欢的。

你的问题的答案

如先前的答案所述,其定义中的 C 和 C++ 函数extern "C"没有被破坏,因此函数的符号根本不包括完整的函数签名。如果您使用 C++ 编译而没有extern "C"损坏函数,那么您可以对它们进行 demangle 以获得完整函数的签名(使用demangler.comc++ library之类的工具)。有关什么是 mangling 的更多详细信息,请参见此处

一般来说,如果您尝试使用 dlopen 导入函数,最好使用第一个选项。

于 2020-12-10T01:14:26.210 回答