Loading Multiple Shared Libraries with Different Versions

Linux accommodates multiple versions of a shared library but what about includes?

Include-files are handled differently from shared libraries:

  • With shared libraries, there will be one name for development packages to use when linking, which is a symbolic link to a file which has a specific soname (name plus version). After linking, a program has a reference to that file which is used whenever running the program.

  • Include-files can be distinguished by the -I option for include-paths. When there are multiple useful versions of a library, some developers may package the versions using different directory names for holding the related header files. That way, changing just the -I option when compiling makes the build scripts able to work with a specific version of the headers. But unlike shared libraries, the header files are used only when building the program which uses a library.

How to load two versions of a library in the same process in Linux?

If you control the code which consumes this library, an easy course of action would be either upgrade/downgrade, so you don't rely on two distinct versions of the same library. If another part of your project also depends on that library or you have the same problem with another third-party library, you will have more work. This can slow down your upgrade path.

If this is a large project and the two endpoints are unrelated, you may want to split them up into separate services/process, with some interprocess communication (IPC) layer in-between. Examples being message-queues, pipes or sockets. This approach is known as the microservices architecture. In some ways, this solution scales better, you have the overhead introduced by the IPC layer, but the new structure may be easier to understand, debug and test.

Another approach is to implement the functionality of this third-party dependency yourself. You can satisfy the requirements for both versions. If the library is open-source, then half of the work is already there. This option provides great flexibility, as you can tailor it to your needs, at the expense of developer overhead.

Your solution of using export mapping is valid but may be difficult to maintain, so I would not recommend it. For large libraries, it may not even be feasible without scripting. What happens when you want to upgrade?

Linking with multiple versions of a library

Thanks for all the responses. I have a solution that seem to be working.
Here's the problem in detail with an example.

In main.c we have:

#include <stdio.h>

extern int foo();

int bar()
{
printf("bar in main.c called\n");
return 0;
}

int main()
{
printf("result from foo is %d\n", foo());
printf("result from bar is %d\n", bar());
}

In foo.c we have:

extern int bar();

int foo()
{
int x = bar();
return x;
}

In bar.c we have:

#include <stdio.h>

int bar()
{
printf("bar in bar.c called\n");
return 2;
}

Compile bar.c and foo.c:

$ gcc -fPIC -c bar.c
$ gcc -fPIC -c foo.c

Add bar.o to a static library:

$ ar r libbar.a bar.o

Now create a shared library using foo.o and link with static libbar.a

$ gcc -shared -o libfoo.so foo.o -L. -lbar

Compile main.c and link with shared library libfoo.so

$ gcc -o main main.c -L. -lfoo

Set LD_LIBRARY_PATH to find libfoo.so and run main:

$ setenv LD_LIBRARY_PATH `pwd`
$ ./main
bar in main.c called
result from foo is 0
bar in main.c called
result from bar is 0

Notice that the version of bar in main.c is called, not the version linked into the shared library.

In main2.c we have:

#include <stdio.h>
#include <dlfcn.h>

int bar()
{
printf("bar in main2.c called\n");
return 0;
}

int main()
{
int x;
int (*foo)();
void *handle = dlopen("libfoo.so", RTLD_GLOBAL|RTLD_LAZY);
foo = dlsym(handle, "foo");
printf("result from foo is %d\n", foo());
printf("result from bar is %d\n", bar());
}

Compile and run main2.c (notice we dont need to explicitly link with libfoo.so):

$ gcc -o main2 main2.c -ldl
$ ./main2
bar in bar.c called
result from foo is 2
bar in main2.c called
result from bar is 0

Now foo in the shared library calls bar in the shared library and main calls bar in main.c

I don't think this behaviour is intuitive and it is more work to use dlopen/dlsym, but it does resolve my problem.

Thanks again for the comments.



Related Topics



Leave a reply



Submit