如何从动态加载库中执行未知函数?

时间:2010-05-12 14:41:13

标签: c++ dynamic load runtime

当您在设计时知道此功能时,可以轻松地从动态库加载函数。 做这样的事情:

   int (*fn)(int);

   l0 = dlopen("./libfoo.so", RTLD_LAZY);
   if (!l0) 
   {
      fprintf(stderr, "l0 %s\n", dlerror());
      return 1;
   }


   fn = (int (*)(int))dlsym(l0, "foo");


   if ((error = dlerror()) != NULL)  
   {
      fprintf(stderr, "fn:%s\n", error);
      return 1;
   }

   x=(*fn)(y);

...

如果在设计时间未知时执行库函数?在运行时,您有一个函数名称和参数指针数组以及参数大小数组:

char * fn_name =“foo”;    int foo_argc;    void * foo_argv [];    int foo_argv_size [];

在脚本语言中,这是一个蛋糕任务,但如何在c ++中很好地实现它?

4 个答案:

答案 0 :(得分:1)

正如问题所述,您无法在标准C ++中执行此操作。除非编译器在编译时知道被调用者的参数,否则无法有效地调用函数。即使在varargs函数的情况下,也必须编译调用代码,知道传递了什么参数 - 没有办法构建va_list

如果您控制dll中的函数,那么最好的方法是传递boost::any的集合(如果两者都将使用相同的编译器和库进行编译),或者void*(如果你没有那么多的兼容性),或参数的一些序列化形式(可能是谷歌的协议缓冲区)。

如果dll已经存在并且您无法更改其界面,那么您需要使用堆栈播放非标准技巧,以根据您的调用约定将参数放在正确的位置(这是动态语言所执行的操作)当他们有一个C调用机制时),否则你需要一个覆盖它可能的所有函数签名的巨大switch语句。显然后者只有在你已经知道你正在处理什么dll的情况下才有可能。

此外,通常您需要的不仅仅是参数及其大小的指针列表。调用约定很可能取决于参数的类型,而不仅仅是大小。例如,浮点参数可以在FPU寄存器中传递,而不是在堆栈中传递。

你还需要担心返回类型,以及调用代码期望用它做什么。

答案 1 :(得分:1)

作为一种静态语言,C ++实际上依赖于在编译时知道类型/接口。我怀疑是否有一个很好的低级方法来完成你想要解决的任务,但是,如果你添加另一层抽象,你可以一如既往地完成目标。特别是在这种情况下我过去使用的方法,当不知道究竟需要将什么“参数”传递给动态加载的插件时,是传递std::map< std::string, std::string > >的单个参数(即键=值字典)只需将其包含在需要传递的任何信息中,然后让插件弄清楚如何处理它。你必须对大部分数据进行编码/解码,但它可以用来优雅地处理我至少运行过的大多数情况,这有点不方便。

当然,这假设您可以通过标准化插件接口来控制动态加载的DLL的API。如果你不能这样做......你可能运气不好。

答案 2 :(得分:1)

实际上有一种方法可以在运行时调用函数,如果你知道它的调用约定以及它接收的参数。然而,这不属于标准的C / C ++语言范围。

对于x86汇编程序:

假设如下:

  1. 您知道要在固定缓冲区准备函数的所有参数,完全按照它们打包在堆栈中的方式。
  2. 您的函数不按值返回/返回C ++对象。
  3. 您可以使用以下功能:

    int CallAnyFunc(PVOID pfn, PVOID pParams, size_t nSizeParams)
    {
        // Reserve the space on the stack
        // This is equivalent (in some sense) to 'push' all the parameters into the stack.
        // NOTE: Don't just subtract the stack pointer, better to call _alloca, because it also takes
        // care of ensuring all the consumed memory pages are accessible
        _alloca(nSizeParams);
    
        // Obtain the stack top pointer
        char* pStack;
        _asm {
            mov pStack, esp
        };
    
        // Copy all the parameters into the stack
        // NOTE: Don't use the memcpy function. Because the call to it
        // will overwrite the stack (which we're currently building)
        for (size_t i = 0; i < nSizeParams; i++)
            pStack[i] = ((char*) pParams)[i];
    
        // Call your function
        int retVal;
        _asm {
            call pfn
            // Most of the calling conventions return the value of the function (if anything is returned)
            // in EAX register
            mov retVal, eax
        };
    
        return retVal;
    }
    

    您可能需要调整此功能,具体取决于使用的调用约定

答案 3 :(得分:0)

  

如果在设计时未知库函数,该如何执行?

你做不到。 C ++中没有任何反映。您必须知道函数原型才能调用它。 (无论如何,除非你实施自己的反思。)