views:

117

answers:

2

I'm dynamically loading (whith dlopen()) a shared object (named libprofile1.so) from main.

In libprofile1.so I have defined factory function CreateProfile and class Profile. CreateProfile function creates an instance of Profile class and returns a pointer to it. Class Profile has a method pMethod.

In main, after loading libprofile1.so, I'm calling CreateProfile method which returns the pointer to the object of Profile class (call it p).
Afterwards, I'm calling pMethod method against object p (p->pMethod). In this method I'm dynamically loading other shared object (libdatasources.so).

In this shared object I have a factory function CreateDataSourceand class DataSource.
CreateDataSource function creates an instance of DataSource class and returns a pointer to it. DataSource class has method dsMethod.

As you can notice, structures of both shared objects are similar.

From pMethod after loading libdatasources.so I'm calling CreateDataSource method, which returns me a pointer to an instance of DataSource class, call it ds. Then I'm calling dsMethod of ds object
(ds->dsMethod).


Now, the problem is following.

When I try to call dsMethod of ds object, shared object that I'm first loading (libprofile1.so) doesn't load. Actually dlopen() returns NULL. When I read dlerror after dlopen I get:

./libprofile1.so: undefined symbol: _ZN18DataSource13dsMethod

So if I have a call ds->Method, than first shared object doesn't load!
If I comment out call ds->dsMethod from the source, then my libprofile1.so and libdatasources.so are loaded without any problems.
I don't see the connection between the call of a method from the second SO, with loading first SO???

Maybe I don't know, but are there any constraints when dynamically loading a shared object, from a shared object that's also been dynamically loaded?

Btw, dlopen is used with RTLD_NOW|RTLD_GLOBAL. I tried with RTLD_LAZY, but still the same problem.

UPDATE:

Libraries are built in Eclipse. Options for G++ compiler and linker are the same for both libraries.
Here are G++ compiler:

-O0 -g3 -Wall -c -fmessage-length=0

and G++ linker:

-shared

options, pasted from Project Properties -> Settings -> Tool Settings

Thanks in advance.

+3  A: 

If you dynamically load a DLL you must make sure that it has no unresolved symbols.

The easiest way to do this is to link it against the other DLLs that it needs so that they load automatically rather than you having to load and resolve all dependencies manually.

So if libprofile1 always uses libdatasources make sure they are linked with each other.

If you must do it manually then reverse the order that you load the libraries. So that when you load libprofile1 the functions that it needs have already been loaded.

Martin York
+4  A: 

As Martin York pointed out, that's the way it works in Linux. When linking against a library, you have to link to all dependencies, too. That's different in Windows, DLLs take care of their dependencies themselves. When dynamically loading a library that has another library as a dependency, you need to load that library first with the RTLD_GLOBAL flag. This is pretty awkard, imho, since you may not be able to know which dependencies another shared objects requires, or the dependencies can change with a newer version that's otherwise binary compatible. From what I know (and from reading the g++ and ld manpages), it is not possible to create a behaviour similar to Windows' DLLs. Here's a little testcase:

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 is compiled into libone.so and two.cpp in libtwo.so, test.cpp is compiled into the test binary. This will fail and only succeed when the commented line is uncommented.

Daniel Albuschat
quite similar example, but the thing is that in `foo()` i'm loading `libtwo.so` and then getting the function pointer to `bar()`, which I call from `foo()` also.I'm using a workaround for this problem with a wrapper function `barWrap()` for `bar()`. `barWrap()` is not the part of the class that I'm using (`DataSource`),but rather exported independent function . From this function I'm calling `bar()` (in my case `ds->dsMethod`) and it works fine. By the way I used `RTLD_NOW|RTLD_GLOBAL` as the argument of `dlopen` but still nothing. Quite a weird problem, I can't seem to figure out.
kobac
Are you linking libone.so against libtwo.so (-ltwo for the linker)? dlopen() should then have no problem loading this. I'll add my gcc command lines as a new comment.
Uli Schlachter
$ 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
Uli Schlachter