从另一个共享对象动态加载共享对象时的限制是什么?

7
我正在使用`dlopen()`来动态加载一个名为`libprofile1.so`的共享对象。在`libprofile1.so`中,我定义了工厂函数`CreateProfile`和类`Profile`。`CreateProfile`函数创建一个`Profile`类的实例并返回一个指向它的指针。`Profile`类有一个方法`pMethod`。
在`main`中,在加载`libprofile1.so`之后,我调用`CreateProfile`方法,该方法返回指向`Profile`类对象(称之为`p`)的指针。然后,我对`p`对象(`p->pMethod`)调用`pMethod`方法。在这个方法中,我动态加载了另一个共享对象(`libdatasources.so`)。
在这个共享对象中,我有一个工厂函数`CreateDataSource`和类`DataSource`。`CreateDataSource`函数创建一个`DataSource`类的实例并返回一个指向它的指针。`DataSource`类有一个方法`dsMethod`。
正如你所看到的,两个共享对象的结构相似。
当我尝试调用`ds`对象的`dsMethod`时,我遇到了以下问题。我首先加载的共享对象(`libprofile1.so`)不会加载。实际上,`dlopen()`返回`NULL`。当我在`dlopen`之后读取`dlerror`时,得到以下信息:
``` ./libprofile1.so: undefined symbol: _ZN18DataSource13dsMethod ```
因此,如果我有一个调用`ds->Method`的调用,那么第一个共享对象就不会加载!如果我从源代码中注释掉对`ds->dsMethod`的调用,则我的`libprofile1.so`和`libdatasources.so`都可以正常加载。我看不出第二个SO的方法调用与第一个SO的加载之间有什么联系??
也许我不知道,但是在动态加载一个共享对象时,从已经被动态加载的共享对象加载另一个共享对象有任何限制吗?
顺便提一下,使用dlopen时需要与RTLD_NOW|RTLD_GLOBAL一起使用。我尝试过使用RTLD_LAZY,但问题仍然存在。
更新:
这些库是在Eclipse中构建的。G++编译器和链接器的选项对于这两个库都是相同的。
以下是G++编译器:
-O0 -g3 -Wall -c -fmessage-length=0

以及 G++ 链接器:

-shared

谢谢您提前阅读。以下是需要翻译的内容:
选项,从“项目属性 -> 设置 -> 工具设置”中复制


你能否更新你的问题,说明如何构建libprofile1.so和libdataresources.so? - Sam Miller
2个回答

4
正如Martin York所指出的那样,在Linux中它就是这样工作的。当链接到一个库时,你必须同时链接到所有依赖项。而在Windows中则不同,DLL会自己处理它们的依赖关系。当动态加载一个具有另一个库作为依赖项的库时,你需要使用RTLD_GLOBAL标志首先加载该库。我认为这相当令人尴尬,因为你可能无法知道另一个共享对象需要哪些依赖项,或者依赖关系可能会随着新版本的二进制兼容而改变。据我所知(以及从阅读g++和ld man页面得知),不可能创建类似于Windows DLL的行为。 以下是一个小测试案例:
two.cpp:
#include <iostream>

extern "C"
{
   void bar()
   {
      std::cout << "bar()\n";
   }
}

one.cpp:

#include <iostream>

extern "C"
{
   void bar();

   void foo()
   {
      std::cout << "foo()\n";
      bar ();
   }
}

test.cpp:

#include <dlfcn.h>
#include <iostream>

int main (int argc, char *argv[])
{
   using namespace std;
//       void *libtwo = dlopen("./libtwo.so", RTLD_NOW | RTLD_GLOBAL);
   void *libone = dlopen("./libone.so", RTLD_NOW);
   if (!libone)
   {
      cout << "dlopen(libone.so) failed: " << dlerror() << "\n";
      return 1;
   }
   typedef void (*foo_t)();
   foo_t foo = reinterpret_cast<foo_t>(dlsym(libone, "foo"));
   if (!foo)
   {
      cout << "dlsym(libone.so, foo) failed\n";
      return 2;
   }
}

one.cpp编译成libone.so,two.cpp编译成libtwo.so,test.cpp编译成test二进制文件。 只有在取消注释的情况下才能成功,否则会失败。


有一个相似的例子,但问题在于我在foo()中加载了libtwo.so,然后获取到了指向bar()的函数指针,并从foo()中调用它。我使用了一个解决此问题的变通方法,即为bar()创建一个包装函数barWrap()barWrap()不是我正在使用的类(DataSource)的一部分,而是导出的独立函数。从这个函数中,我调用bar()(在我的情况下是ds->dsMethod),它可以正常工作。顺便说一句,我使用了dlopen的参数RTLD_NOW|RTLD_GLOBAL,但仍然没有效果。这是一个相当奇怪的问题,我似乎无法解决。 - dragan.stepanovic
您是否将libone.so链接到libtwo.so(-ltwo用于连接器)?这样,dlopen()就不会有问题加载它。我将把我的gcc命令行添加为新注释。 - Uli Schlachter
1
$ g++ -shared -fpic -o libtwo.so -Wl,-no-undefined two.cpp $ g++ -shared -fpic -o libone.so -Wl,-no-undefined one.cpp -ltwo -L. -Wl,-rpath,. $ g++ test.cpp -ldl $ ./a.out(注:此为一段程序相关的命令行代码)以上是一段程序相关的命令行代码,用于编译和运行程序。其中包括了编译两个动态链接库文件 libone.so 和 libtwo.so 的命令,以及编译测试程序 test.cpp 的命令,并最终通过运行 ./a.out 来执行程序。 - Uli Schlachter

4
如果你动态加载DLL文件,你必须确保它没有未解决的符号。
最简单的方法是将其与其他所需的DLL链接起来,这样它们就会自动加载,而不需要手动加载和解决所有依赖关系。
因此,如果libprofile1总是使用libdatasources,请确保它们彼此链接。
如果必须手动执行,则反转加载库的顺序。这样当你加载libprofile1时,它需要的函数已经被加载了。

如果图书馆已经与主程序链接在一起,会怎么样?我发现,如果我将我的dlopened库与其他也与主程序链接的库链接在一起,加载.so文件时它们会出现多个副本,例如全局变量会出现两次。如果我不将我的dlopended库与共享库链接在一起,在运行时会出现链接错误,因为它找不到符号。必须有一种简单的方法来解决这个问题。我认为我需要像主应用程序那样的RTLD_GLOBAL标志。 - brookbot
哎呀,没事了。我只是需要在主链接步骤中添加-rdynamic链接器标志。问题解决了。 - brookbot

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