我的macOS Objective-C/Swift应用程序有一个问题。我使用的一个库出现了问题,我不知道什么时候会有新版本。
根据我的发现,我使用的一个方法在上一个版本的库中已经修复了,但是这个最新版本在另一个方法中引入了一个错误,该方法在上一个版本中工作。
在考虑可能的解决方案时,我认为理论上可以链接两个库,并在两个库中使用有效的,但这当然会导致所有符号被定义两次,并且无法编译。
有没有可能改变其中一个版本的符号名称,并引入前缀之类的东西,这样我就可以决定在不同的情况下使用哪个版本?对不起,如果这是完全没有意义的,也许这取决于库的具体实现。
您可以修补符号(只需在十六进制编辑器中搜索字符串并更改一些字节就可以了,只要总长度保持不变),但我认为有一个更优雅的解决方案。
Mach-Os记录他们想从哪个库导入哪个符号,默认情况下在非平面命名空间中运行,即来自不同库的符号在运行时不会相互冲突。
正如您可能已经观察到的那样,它们确实在链接时发生冲突。但是在链接时搞乱东西要比修补二进制文件容易得多。
我假设你的两个库都有相同的"安装名"。(如有疑问,检查otool -l your.dylib | fgrep -A2 LC_ID_DYLIB
)。如果是这种情况,那么您必须重命名其中一个。如果dylib的原始安装名称是/usr/local/lib/libstuff.dylib
,那么将其中一个重命名为/usr/local/lib/libstuff_alt.dylib
,并在其上运行以下命令:
install_name_tool -id /usr/local/lib/libstuff_alt.dylib /usr/local/lib/libstuff_alt.dylib
如果您的库已经或需要签名,您现在需要重新签名:
codesign -f -s - /usr/local/lib/libstuff_alt.dylib
如果你对安装名是如何工作的感到好奇,请看我的回答。
一旦两个版本的库具有不同的名称,让我们做一个您可以遵循的设置。我创建了以下C文件:
a.c
:
int f(void)
{
return 10;
}
int g(void)
{
return 11;
}
b.c
:
int f(void)
{
return 20;
}
int g(void)
{
return 21;
}
并将它们都编译为库:
cc -shared -o liba.dylib a.c -Wall -O3
cc -shared -o libb.dylib b.c -Wall -O3
然后我创建了另一个名为t.c
的文件,使用f()
和g()
函数:
#include <stdio.h>
extern int f(void);
extern int g(void);
int main(void)
{
printf("%d %dn", f(), g());
return 0;
}
如果你编译并链接这两个库,那么它将从你首先指定的库中导入这两个符号:
% cc -o t t.c -Wall -O3 -L. -la -lb
% ./t
10 11
% cc -o t t.c -Wall -O3 -L. -lb -la
% ./t
20 21
所以我们要做的是稍微欺骗一下,使用"基于文本的存根文件"。可用于链接而不是实际的dylib。
xcrun tapi stubify -o liba.tbd liba.dylib
xcrun tapi stubify -o libb.tbd libb.dylib
创建liba.tbd
和libb.tbd
文件,如下所示:
--- !tapi-tbd
tbd-version: 4
targets: [ arm64-macos ]
uuids:
- target: arm64-macos
value: 2AACA829-4039-3B2A-8751-2AB617189F29
flags: [ not_app_extension_safe ]
install-name: liba.dylib
current-version: 0
compatibility-version: 0
exports:
- targets: [ arm64-macos ]
symbols: [ _f, _g ]
...
--- !tapi-tbd
tbd-version: 4
targets: [ arm64-macos ]
uuids:
- target: arm64-macos
value: 02EE57B9-3074-3EE7-8B3C-EF2BDFA1D26F
flags: [ not_app_extension_safe ]
install-name: libb.dylib
current-version: 0
compatibility-version: 0
exports:
- targets: [ arm64-macos ]
symbols: [ _f, _g ]
...
此时,我们可以轻松地从这些文件中删除我们不想要的符号。在我的例子中,我确保liba.tbd
只有[ _f ]
,libb.tbd
只有[ _g ]
。一旦完成,我们可以再试一次:
% cc -o t t.c -Wall -O3 -L. -la -lb
% ./t
10 21
就这样了。
这样做的唯一警告是,你需要确保你使用的函数对它们来自的库没有任何类型的内部依赖,比如全局变量。