使用多个版本的库进行链接

34

我有一个应用程序,它与第三方供应商VENDOR1提供的库libfoo的版本X进行静态链接。它还链接了一个动态(共享)库libbar,该库来自不同的第三方供应商VENDOR2,并且使用VENDOR1的libfoo的版本Y进行静态链接。

因此,libbar.so包含版本Y的libfoo.a,而我的可执行文件包含版本X的libfoo.a。libbar仅在内部使用libfoo,我的应用程序中没有传递任何libfoo对象给libbar。

在构建时没有错误,但在运行时应用程序会崩溃。原因似乎是版本X使用具有不同大小的结构体,而版本Y使用其他结构体,而运行时链接器似乎混淆了哪个结构体被使用。

VENDOR1和VENDOR2都是闭源的,因此我无法重新构建它们。

有没有一种方法可以构建/链接我的应用程序,使它始终解析为版本X,而libbar始终解析为版本Y,两者永远不会混合使用?


你能让你的应用程序动态链接到VENDOR1吗? - Billy ONeal
这段与编译器、链接器和操作系统如何协同工作有关的编程内容非常具体。最简单的方法是给两个供应商发送电子邮件,看看他们是如何解决这个问题的。 - Martin York
我们目前的想法是,在Linux上至少使用带有RTLD_DEEPBIND标志的dlopen()来加载libbar.so。另一个可能性是将应用程序对libfoo.a的使用分离出来,形成一个名为libbaz.so的共享库,该库包装了对libfoo.a的使用,然后让应用程序使用RTLD_LOCAL来dlopen libbaz.so和libbar.so,我们认为这可以保持所有重复符号的内部。这在Linux上可能有效,但我们需要它在Solaris、AIX和HPUX上也能工作。 - YerBlues
哦,呸。不幸的是,在Linux上,没有“简单”的解决方法。 - Tim Post
3个回答

20
谢谢所有的回复。我有一个解决方案似乎可以工作。 这是具体问题及示例。
在main.c中我们有:
#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());
}

在 foo.c 文件中,我们有:

extern int bar();

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

我们在bar.c中有以下代码:

#include <stdio.h>

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

编译 bar.c 和 foo.c:

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

将bar.o添加到静态库中:

$ ar r libbar.a bar.o
现在,使用foo.o创建一个共享库,并与静态库libbar.a链接。
$ gcc -shared -o libfoo.so foo.o -L. -lbar

编译 main.c 并链接共享库 libfoo.so

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

设置LD_LIBRARY_PATH以查找libfoo.so并运行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

请注意,main.c中的bar版本被调用,而不是链接到共享库中的版本。

在main2.c中我们有:

#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());
}

编译并运行main2.c文件(注意我们不需要显式地链接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

现在共享库中的foo调用了共享库中的bar,而主程序在main.c中调用了bar。

我认为这种行为并不直观,使用dlopen / dlsym 需要更多的工作,但它解决了我的问题。

再次感谢您的意见。


非常感谢您的提示,但您能否解释一下为什么要使用RTLD_GLOBAL,如果整个目的是隔离库之间的呢? - ScumCoder

8
尝试使用部分链接,以便您拥有一个带有libbar和libfoo-Y的对象文件“partial.o”。使用“--localize-symbols”选项的objcopy将来自libfoo-Y的符号变为本地符号。您应该能够通过在libfoo-Y上运行nm并调整输出来生成。然后将修改后的partial.o链接到您的应用程序中。我曾在vxWorks上使用gcc工具链执行类似操作,其中动态库不是问题,但同一库的两个版本需要干净地链接到单片应用程序中。

2
抱歉,不行。我的理解是Linux(以及可能大多数*nix)的方式不允许这种情况发生。我能想到的唯一“解决办法”是创建一个代理应用程序,以某种IPC形式公开libbar中所需的内容。您可以使用LD_LIBRARY_PATH或类似的东西使该代理加载正确的版本。

除了AIX之外,可能大多数*nix系统都是如此。在AIX上,这实际上是可能的,并且默认的链接器行为恰好符合问题所要求的。 - Dummy00001
2
OS X 也很好地处理了这个问题。每个.so/.dylib 都有自己的链接表/引用。我说“大多数”是因为我知道并非全部如此。无论如何,据我所知,Linux 不会这样做。 - Gianni

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