dlsym/dlopen与运行时参数

adk*_*adk 6 c dlopen

我正在尝试做类似以下的事情

  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>>
  }
Run Code Online (Sandbox Code Playgroud)

伪代码就像

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
Run Code Online (Sandbox Code Playgroud)

谢谢

P S*_*ved 21

实际上,你几乎可以做任何你想做的事.在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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

你必须确保的是你总是用它应该接受的参数的确切数量和类型来调用函数.密切关注不同整数类型之间的差异(您最好的选择是明确地向它们投射参数).

一些评论者报告说,上述代码不能保证适用于可变功能(例如printf).

  • @Pavel:对不起,我想你需要阅读语言规范."不应该依赖"并不是某些东西有效的论据C.在C中,它是在没有正确原型的情况下调用可变参数函数的显式未定义行为,并且它将在许多逐个寄存器架构上中断(我相信除了x86_64之外)为了愚蠢而愚蠢地跳过箍,使得它在没有`stdio.h`的情况下调用`printf`)或者使用"PASCAL"(又名"stdcall")调用约定的任何实现. (2认同)

Jon*_*ler 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
Run Code Online (Sandbox Code Playgroud)

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

请注意,您无法像动态那样动态构建函数调用 - 或者不是一般情况下.要做到这一点,需要做更多的工作.您必须提前知道函数指针在参数的方式和它返回的内容以及如何解释它们时所期望的内容.

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

C中的反思很难 - 很难.它不可撤销 - 但它需要基础设施来支持它并遵守规则使用它,它只能调用支持基础设施规则的功能.