【问题标题】:How can I link a program to two libraries with the same function name but different implementations?如何将程序链接到具有相同函数名但实现不同的两个库?
【发布时间】:2021-04-19 09:06:41
【问题描述】:

我正在使用库 foo1.afoo2.a 编写程序。

foo2.a内部,它使用了foo3.a,它实现了一个函数funcfoo。但是foo1.a 包含实现自己的funcfoo 的相同函数。我的主程序想要使用来自foo1.afuncfoo,以及来自foo2.a 的一些其他功能,同时还要确保foo2.a 只使用来自foo3.afuncfoo

我是否可以强制执行此操作?

【问题讨论】:

  • 重命名一个funcfoo?
  • 我真的做不到。它在这两个地方都广泛使用。
  • 使用objcopy等自动化工具重命名funcfoo?
  • 注意:如果库没有为标识符使用正确的命名前缀(例如:LibraryName_FunctionName,这强烈表明库编写得不好并且可能还有其他严重问题。应该替换它尽快找到更好的东西。
  • @user694733 这通常是一个好点,但并不总是正确的做法。 -- 无论如何,如果已经用这些库构建了其他应用程序,那么重命名是不可能的。 -- 现代 IDE 甚至不太简单的编辑器都可以毫不费力地重命名一大堆命中。

标签: c gcc linker static-libraries


【解决方案1】:

简短回答:你不能

长答案:您仍然不能,因为名称(如函数名称以及全局变量或枚举名称......任何名称)在您的二进制文件中必须是唯一的[1]

但是

也许你可以采取一些行动。

  1. 如果您拥有涉及此混乱的任何库的源代码,请使用相同的内容创建一个完整的新库,但已重命名。

基本上,我说“为库创建一个新的主要版本,因为您将更改库中存在的函数名称,从而破坏了复古兼容性”。 如果您的库是包含函数“min”的“foo1.a”,则使用“foo2_min”函数创建一个“foo2.a”。 用某些东西为内部函数(无论是在库中还是直接在二进制文件中)添加前缀/后缀通常是一种很好的做法。就像您的公司名称是“我的小马驹”一样,“MLP_”听起来是一个不错的前缀(以防万一,做一个谷歌搜索)。 这就是user694733在评论中所说的。

如果您使用的是 IDE,那么使用“重命名重构”功能应该可以轻松快捷地完成此操作。 我建议尽可能多地进行重命名,以避免出现您现在陷入困境的情况。

  1. 您没有源代码,但库许可证允许您对其进行修改。

如果它的格式是开放的(就像旧的那样),你可以使用 objcopy。 我读过它可以做到这一点,但我自己从来没有这样做过......祝你好运

如果它是一种接近的格式,要么你有关于它的文档并且你必须做很多工作,要么你完全被卡住了。

  1. 第三方拥有代码源,和/或库许可证不允许您修改它

如果是第三方库,可能你的公司已经订阅了他的支持,所以你最好直接联系他们。

.

据我所知,在 C 语言中不可能将库“封装”成“空间名称”。

我希望这个答案对你有所帮助,我希望它是准确和完整的。

[1] 好吧,这并不完全正确,因为您可以使用全局变量的名称创建一个局部变量,这将编译并运行。在本地上下文中,将使用本地变量。 gcc 可以使用 -Wshadow 选项警告这种情况。 但除非你正在做一些不正当的黑客行为,否则这种情况通常是你想要避免的。

【讨论】:

    【解决方案2】:

    当您在命令行中提供库时,大多数链接器都遵循库的顺序。如果你首先用“foo1.a”链接你的程序,它将通过“foo1.a”的实现来解析你程序中funcfoo的引用。将“foo2.a”放在第二个位置,这将留下对funcfoo 的开放引用。将“foo3.a”放在第三位,这将通过第二个实现来解决。

    编辑 1:

    恐怕我错了。快速检查(就在睡觉之前)显示,当“foo2.a”被链接时,对funcfoo 的引用通过已经加载的“foo1.a”的实现来解决。 :-(

    我会做更多的研究,但请不要屏住呼吸。

    编辑 2:

    好的,这花了一些时间,但在“objcopy”的帮助下,工作正常。即使在库中,您也可以使用其选项 --redefine-sym old=new 来“重命名”符号。

    我准备了一个小示例来遵循这些步骤作为概念证明。在实际项目中,库已经构建,因此只需要显示列表的最后一个命令。

    让我们从主要来源开始:

    #include "foo1.h"
    #include "foo2.h"
    
    int main(void) {
      funcfoo();
      funcbar();
      return 0;
    }
    

    它包括以下头文件,第一个“foo1.h”和第二个“foo2.h”:

    #ifndef FOO1_H
    #define FOO1_H
    void funcfoo(void);
    #endif
    
    #ifndef FOO2_H
    #define FOO2_H
    void funcbar(void);
    #endif
    

    当然两者都有实现,第一个是“foo1.c”,第二个是“foo2.c”:

    #include <stdio.h>
    
    #include "foo1.h"
    
    void funcfoo(void) {
      puts("funcfoo() in foo1");
    }
    
    #include "foo2.h"
    #include "foo3.h"
    
    void funcbar(void) {
      funcfoo();
    }
    

    第三个库“foo3”实现了和“foo1”一样的功能,先是头文件,然后是实现文件:

    #ifndef FOO3_H
    #define FOO3_H
    void funcfoo(void);
    #endif
    
    #include <stdio.h>
    
    #include "foo3.h"
    
    void funcfoo(void) {
      puts("funcfoo() in foo3");
    }
    

    这些是构建应用程序的命令:

    gcc -c -Wall -Wextra foo1.c -o foo1.o
    ar cr libfoo1.a foo1.o
    
    gcc -c -Wall -Wextra foo2.c -o foo2.o
    ar cr libfoo2.a foo2.o
    
    gcc -c -Wall -Wextra foo3.c -o foo3.o
    ar cr libfoo3.a foo3.o
    
    # In the real prject, only the following steps are needed:
    gcc -c -Wall -Wextra main.c -o main.o
    objcopy --redefine-sym funcfoo=funcfoo2 libfoo2.a libfoo2n.a
    objcopy --redefine-sym funcfoo=funcfoo2 libfoo3.a libfoo3n.a
    gcc main.o -L. -lfoo1 -lfoo2n -lfoo3n -o app
    

    如您所见,常用“binutils”的工具“objcopy”用于将冲突函数的名称从funcfoo重新定义为funcfoo2。我还让它创建一个新的输出库来保存原始文件。

    当运行时,应用程序会打印:

    funcfoo() in foo1
    funcfoo() in foo3
    

    【讨论】:

    • 我一直认为您必须指定 LD_PRELOAD 才能做到这一点?没有它,链接器仍然会接受重复的符号?
    • LD_PRELOAD 改变动态加载库的行为。在这种情况下,我们有静态链接库。 -- 这个技巧也可以用来替换标准函数。如果您将应用程序与您自己实现的目标文件(例如strcpy())链接,它将用于在链接中解析打开的引用此时。毕竟,库“只是”一组对象模块。
    猜你喜欢
    • 2021-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-22
    • 1970-01-01
    • 2015-03-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多