至少在静态库的情况下,你可以很方便地解决它。
考虑库foo和bar的头文件。为了本教程的方便,我也会给你源文件。
examples/ex01/foo.h
int spam(void);
double eggs(void);
示例/ex01/foo.c(可能是不透明/不可用的)
int the_spams;
double the_eggs;
int spam()
{
return the_spams++;
}
double eggs()
{
return the_eggs--;
}
example/ex01/bar.h
int spam(int new_spams);
double eggs(double new_eggs);
示例/ex01/bar.c(可能不透明/不可用)
int the_spams;
double the_eggs;
int spam(int new_spams)
{
int old_spams = the_spams;
the_spams = new_spams;
return old_spams;
}
double eggs(double new_eggs)
{
double old_eggs = the_eggs;
the_eggs = new_eggs;
return old_eggs;
}
我们希望将它们用于程序 foobar。
示例/ex01/foobar.c
#include <stdio.h>
#include "foo.h"
#include "bar.h"
int main()
{
const int new_bar_spam = 3;
const double new_bar_eggs = 5.0f;
printf("foo: spam = %d, eggs = %f\n", spam(), eggs() );
printf("bar: old spam = %d, new spam = %d ; old eggs = %f, new eggs = %f\n",
spam(new_bar_spam), new_bar_spam,
eggs(new_bar_eggs), new_bar_eggs );
return 0;
}
一个问题立即显现:C语言不支持重载。因此,我们有两个相同名称但签名不同的函数。所以我们需要一些方法来区分它们。无论如何,让我们看看编译器对此的反应:
example/ex01/ $ make
cc -c -o foobar.o foobar.c
In file included from foobar.c:4:
bar.h:1: error: conflicting types for ‘spam’
foo.h:1: note: previous declaration of ‘spam’ was here
bar.h:2: error: conflicting types for ‘eggs’
foo.h:2: note: previous declaration of ‘eggs’ was here
foobar.c: In function ‘main’:
foobar.c:11: error: too few arguments to function ‘spam’
foobar.c:11: error: too few arguments to function ‘eggs’
make: *** [foobar.o] Error 1
好的,这并不意外,它只是告诉我们我们已经知道或者至少怀疑的内容。
那么我们能否在不修改原始库源代码或头文件的情况下解决标识符冲突呢?实际上我们可以。
首先让我们解决编译时问题。为此,我们使用一堆预处理器#define
指令来给库导出的所有符号添加前缀。稍后我们会使用一些漂亮舒适的包装头文件来完成这个操作,但是为了演示正在进行的工作,我们将在foobar.c源文件中直接进行操作:
example/ex02/foobar.c
#include <stdio.h>
#define spam foo_spam
#define eggs foo_eggs
# include "foo.h"
#undef spam
#undef eggs
#define spam bar_spam
#define eggs bar_eggs
# include "bar.h"
#undef spam
#undef eggs
int main()
{
const int new_bar_spam = 3;
const double new_bar_eggs = 5.0f;
printf("foo: spam = %d, eggs = %f\n", foo_spam(), foo_eggs() );
printf("bar: old spam = %d, new spam = %d ; old eggs = %f, new eggs = %f\n",
bar_spam(new_bar_spam), new_bar_spam,
bar_eggs(new_bar_eggs), new_bar_eggs );
return 0;
}
现在,如果我们编译这个代码...
example/ex02/ $ make
cc -c -o foobar.o foobar.c
cc foobar.o foo.o bar.o -o foobar
bar.o: In function `spam':
bar.c:(.text+0x0): multiple definition of `spam'
foo.o:foo.c:(.text+0x0): first defined here
bar.o: In function `eggs':
bar.c:(.text+0x1e): multiple definition of `eggs'
foo.o:foo.c:(.text+0x19): first defined here
foobar.o: In function `main':
foobar.c:(.text+0x1e): undefined reference to `foo_eggs'
foobar.c:(.text+0x28): undefined reference to `foo_spam'
foobar.c:(.text+0x4d): undefined reference to `bar_eggs'
foobar.c:(.text+0x5c): undefined reference to `bar_spam'
collect2: ld returned 1 exit status
make: *** [foobar] Error 1
起初看起来情况变得更糟了。但仔细观察,实际上编译阶段进行得很顺利。只是链接器现在抱怨有符号冲突,并告诉我们发生这种情况的位置(源文件和行)。正如我们所看到的那样,这些符号没有前缀。
让我们使用 nm 实用程序查看符号表:
example/ex02/ $ nm foo.o
0000000000000019 T eggs
0000000000000000 T spam
0000000000000008 C the_eggs
0000000000000004 C the_spams
example/ex02/ $ nm bar.o
0000000000000019 T eggs
0000000000000000 T spam
0000000000000008 C the_eggs
0000000000000004 C the_spams
现在我们面临的挑战是将这些符号添加到一些不透明的二进制文件中。是的,我知道在这个例子中我们有源代码并且可以在那里进行更改。但是现在,假设你只有那些.o文件或一个.a文件(实际上只是一堆.o文件)。
救命稻草:objcopy
有一个特别适合我们的工具:objcopy
objcopy在临时文件上运行,因此我们可以将其用作就地操作。有一个选项/操作称为--prefix-symbols,您猜它是做什么的。
因此,让我们将这个家伙扔到我们的固执库中:
example/ex03/ $ objcopy --prefix-symbols=foo_ foo.o
example/ex03/ $ objcopy --prefix-symbols=bar_ bar.o
nm向我们展示它似乎起作用了:
example/ex03/ $ nm foo.o
0000000000000019 T foo_eggs
0000000000000000 T foo_spam
0000000000000008 C foo_the_eggs
0000000000000004 C foo_the_spams
example/ex03/ $ nm bar.o
000000000000001e T bar_eggs
0000000000000000 T bar_spam
0000000000000008 C bar_the_eggs
0000000000000004 C bar_the_spams
让我们尝试将整个内容链接起来:
example/ex03/ $ make
cc foobar.o foo.o bar.o -o foobar
事实上,它确实奏效了:
example/ex03/ $ ./foobar
foo: spam = 0, eggs = 0.000000
bar: old spam = 0, new spam = 3
现在我让读者自己动手编写一个工具/脚本,使用
nm 自动提取库的符号,并编写一个包装头文件来描述这个结构。
#define spam foo_spam
#define eggs foo_eggs
#include <foo.h>
#undef spam
#undef eggs
使用objcopy将符号前缀应用于静态库的对象文件。
那么共享库呢?
原则上,共享库也可以这样做。但是,共享库(名称就是这个意思)被多个程序共享,因此以这种方式搞乱共享库并不是一个好主意。
您无法绕过编写跳板包装器。更糟糕的是,您不能在对象文件级别上链接共享库,而是被迫进行动态加载。但是,这值得一篇专门的文章。
敬请关注,愉快编码。