Mac工程动态库配置和加载探究

2024-09-05 05:04

本文主要是介绍Mac工程动态库配置和加载探究,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

缘起

最近在做Mac程序的打包,其中涉及到Mac程序引用了Hoops的第三方动态库。在之前的工程配置中,Project的Run Script是这么来处理动态库的:

FRAMEWORKS_DIR=${TARGET_BUILD_DIR}/${EXECUTABLE_NAME}.app/Contents/Frameworks/
mkdir -p ${FRAMEWORKS_DIR}if [ -f ${TARGET_BUILD_DIR}/libhps_core.dylib -a -f ${TARGET_BUILD_DIR}/libhps_sprk.dylib -a -f ${TARGET_BUILD_DIR}/libhps_sprk_ops.dylib]};
then
LIB_DIR=${TARGET_BUILD_DIR}
else
LIB_DIR=${NL_GIT_EXTERNAL_DIR_MAC}/HoopsExchange/osx/2019/lib
ficp  ${LIB_DIR}/libhps_core.dylib ${FRAMEWORKS_DIR}
cp  ${LIB_DIR}/libhps_sprk.dylib ${FRAMEWORKS_DIR}
cp  ${LIB_DIR}/libhps_sprk_ops.dylib ${FRAMEWORKS_DIR}
cp  ${LIB_DIR}/libhps_sprk_exchange.dylib ${FRAMEWORKS_DIR}
cp  ${LIB_DIR}/libhps_sprk_exchange_parasolid.dylib ${FRAMEWORKS_DIR}
cp  ${LIB_DIR}/libA3DLIBS-12.2.20.dylib ${FRAMEWORKS_DIR}
cp  ${LIB_DIR}/libA3DLIBS.dylib ${FRAMEWORKS_DIR}
cp  ${LIB_DIR}/libA3DLIBSCpp.dylib ${FRAMEWORKS_DIR}

上面的脚本的意思是,在生成Mac程序后,将引用到的libhps动态库copy到程序包的framework目录,以便让hoops动态库和程序一起打包,从而在其他机器上也能够让dyld找到并加载程序所依赖的动态库。
而Apple规定的的Mac程序包所引用的动态库的存放位置应该是(包括动态库和静态库):

@executable_path/…/Frameworks
@executable_path 代表你Mac 程序可执行文件的位置

如果你的App名称为MyExe,则上面的路径则对应为:

MyExe.app/Contents/Frameworks

从这点看,之前工程脚本写的也没问题。但当真正build后,XCode就会报错:CodeSign Empty.
这是因为在XCode引入CodeSign机制后, 会默认对程序包里面的二进制文件检查其CodeSign,这种直接copy进去的动态库,当然是没有经过签名的,所以XCode会报错。

解决思路1

既然是动态库缺失CodeSign的问题,那就想办法解决他。第一种思路是,既然XCode只会检测导入到程序包里面文件的签名,那么我们就不要将libhps这些动态库copy到包里面。解决方法是:

  1. 把copy脚本删除
  2. 通过设置Xcode的Build Setting里面的Library Search Paths,指定hoops库的位置,让Xcode能够在程序包外找到所引用的动态库

重新build,运行,发现这时候程序正常启动,问题似乎得到了解决。
但是当我们把程序包copy到其他的机器上,再次运行,会发现dyld会报找不到动态库的错误。
出现这种错误是当然的,因为在Library Search Paths中我们只是指定了本机路径,在其他的机器上如果路径不存在的话,当然就找不到动态库了。

解决这个问题的方法也很简单,就是在其他机器的相同路径下,也放置动态库文件。

解决思路2

在思路1中,虽然可以通过在相同路径下放置动态库文件解决library not found的问题,但是如果App要上架APP store,就不太容易在客户机器上放置动态库文件了。
另一中解决思路仍是将用到的库文件copy到程序包中,但这次我们直接将动态库拖拽到工程中,并选择Embed & Sign:
在这里插入图片描述

通过这种方式,Xcode会自动完成Code Sign并将库文件copy到app的framework下。这下,库文件就可以随着程序包走了。

但这个时候试着运行程序,还是会在运行时抛出library not found的错误。这是为什么呢?动态库明明已经embed到我们的APP中,为什么dyld还是会找不到呢?

这里就需要了解一下Mach-O文件对动态库的加载过程。

Mach-O文件的动态库加载

我们知道,在Apple平台上的库文件和执行文件都是Mach-O格式的。在Mach-O文件中,有LOAD COMMAND字段,里面写有很多加载命令,来指导dyld如何将我们的执行文件加载到内存中运行。

其中,我们可以通过查看LOAD COMMAND下的LC_LOAD_DYLIB字段来查看程序需要加载的动态库。具体是可以通过在终端输入otool命令,来查看程序需要加载的动态库。比如一个App名字叫做WaffleOMatic,他引用了一个动态库WaffleVarnish.framework/WaffleVarnish,就可以用otool命令来查看APP所引用的动态库:

otool -l WaffleOMatic.app/WaffleOMatic | grep -A 2 LC_LOAD_DYLIB

输出是:

    cmd LC_LOAD_DYLIB
cmdsize 72name @rpath/WaffleVarnish.framework/WaffleVarnish …

这里可以看到,我们确实可以看到所引用的动态库WaffleVarnish.framework/WaffleVarnish。但是他的name是@rpath/WaffleVarnish.framework/WaffleVarnish,这个name是怎么来的呢?

这个name被称作install name,它是在开发动态库的时候,在动态库工程的Dynamic Library Install Name选项中设置的。dyld就依据这个install name路径来找到并加载动态库的。在开发动态库的时候,可以将install name设置为一个具体的路径,来确保dyld能够在具体路径下找到这个动态库。而这里的WaffleVarnish的install name则被设置为了@rpath/WaffleVarnish.framework/WaffleVarnish。

这个@rpath表示什么呢?其实,在开发动态库的时候,我们都应该将install name设置为@rpath开头,而不要使用具体的路径。因为@rpath是可以在引用动态库的工程中进行设置的,这就可以让所有引用你所开发的动态库的工程,都可以灵活的放置你的动态库而不需要局限在某个具体路径中。

比如WaffleOMatic工程引用了WaffleVarnish动态库,就可以在WaffleOMatic工程的Building Setting->Runpath Search Paths 中设置@rpath所替换的具体的路径。注意,这里@rpath可以设置多个值,dlyd就会依次寻找这些路径下是否存在对应的动态库。

在这里插入图片描述

回到我在实际工作中遇到的问题,当我把Hoops库通过Embed & Sign拖拽到工程中后,虽然在程序包中可以保证动态库的存在并被签名,但是当查看Hoops库的install name后会发现,他的值也是被设置为了@rpath的形式:

cmd LC_ID_DYLIB
cmdsize 64
name @rpath/libhps_sprk_exchange.dylib

这本意是为了让我们能够灵活地放置Hoops库文件,但是由于之前并不了解这个@rpath,我们并未在工程中设置@rpath的值,所以导致了Hoops动态库无法被找到。解决方法就是在Building Setting->Runpath Search Paths 中设置@rpath的值。这里我这设置为了

@executable_path/../Frameworks

@executable_path是一个系统变量,表示可执行文件所在的path,@executable_path/../Frameworks正好对应embed后动态库所在的path。通过设置后,再重新run程序,就可以正常启动了。

总结一下,动态库在Mac程序中的加载过程:

  1. 在打包程序时,所有引用的到的动态库的install name,都会被静态链接到程序的Mach-O文件中(其存储位置在LOAD COMMAND字段的LC_LOAD_DYLIB中)。
  2. 当程序被加载到内存,dyld会遍历Mach-O文件LC_LOAD_DYLIB字段下的值,根据每条记录的install name值来确定加载动态库的路径。
  3. 对于@rpath形式的install name,dlyd还会去Mach-O文件的LC_RPATH字段下读取我们在工程中所设置的Runpath Search Paths 的值,用这些具体的值来替换@rpath作为动态库的install name。
  4. dlyd依次根据install name的值来尝试寻找并加载动态库。

几个有用的命令

通过读取Mach-O相关的加载命令字段,我们可以了解程序对动态库的设置。下面记录些有用的命令:

  • 查看程序所有引用的动态库:
otool -l WaffleOMatic.app/WaffleOMatic | grep -A 2 LC_LOAD_DYLIB
  • 查看动态库的install name
otool -l WaffleOMatic.app/Frameworks/WaffleVarnish.framework/WaffleVarnish | grep -A 2 LC_ID_DYLIB
  • 查看程序所配置的@rpath的值
otool -l WaffleOMatic.app/WaffleOMatic | grep -A 2 LC_RPATH

Symbolic link在动态库中的应用

在使用Hoops库的时候,会注意到在Hoops的lib文件夹下,存在一个软链接libA3DLIBS.dylib,指向libA3DLIBS.24.2.0.dylib这个动态库。
在这里插入图片描述

而在Hoops代码内部,也是引用libA3DLIBS.dylib这个动态库,而不是实际的libA3DLIBS.24.2.0.dylib。这是为什么呢?这是一个巧妙的设计,因为libA3DLIBS可能会有不同的版本,如在2019的Hoops库中,这个文件是libA3DLIBS-12.2.20.dylib。所以通过软链接libA3DLIBS.dylib,就可以屏蔽不同版本的动态库名称,在代码内部始终使用libA3DLIBS.dylib这个名称来使用动态库。

但这里就会有个问题, Symbolic link文件是不能够被embed到程序包中的,因此当我们程序在运行时,到了引用libA3DLIBS.dylib的地方,还是会报library not found的问题。这就需要在Build Phases->Run Script中创建一个Symbolic link:

FRAMEWORKS_DIR=${TARGET_BUILD_DIR}/${EXECUTABLE_NAME}.app/Contents/Frameworks/
mkdir -p ${FRAMEWORKS_DIR}
ln -s ${FRAMEWORKS_DIR}/libA3DLIBS.24.2.0.dylib ${FRAMEWORKS_DIR}/libA3DLIBS.dylib

如何修改一个已经存在的dylib的install name

因为dyld会根据动态库的install name来加载动态库,如果我们所引用的三方动态库的install name设置错了该怎么办呢?我们无法修改三方库的工程配置,但所幸的是可以使用命令install_name_tool来修改install name。

@rpath的妙用

Swift System Libraries

Modern systems have the Swift system libraries built-in. If your app supports older systems, Xcode embeds a copy of these libraries within your app. It uses rpath magic to ensure that your app uses the built-in system libraries if they’re available, falling back to the embedded ones if they’re not.
To demonstrates how this works, change the deployment target for the WaffleOMatic app and the WaffleVarnish framework to iOS 12. Also add some trivial Swift code to the framework. The app now includes a copy of the Swift system libraries:

% ls -l WaffleOMatic.app/Frameworks
total 79032
drwxr-xr-x  … WaffleVarnish.framework
-rwxr-xr-x  … libswiftCore.dylib
…

Each library has an rpath-relative install name:

% otool -l WaffleOMatic.app/Frameworks/libswiftCore.dylib | grep -A 2 LC_ID_DYLIBcmd LC_ID_DYLIBcmdsize 52name @rpath/libswiftCore.dylib …

The app also has a new LC_RPATH load command for /usr/lib/swift:

% otool -l WaffleOMatic.app/WaffleOMatic | grep -A 2 LC_RPATHcmd LC_RPATHcmdsize 32path /usr/lib/swift …
--cmd LC_RPATHcmdsize 40path @executable_path/Frameworks …

The placement of this load command is critical. By placing it first in the list, the dynamic linker will use the built-in Swift system libraries in preference to the embedded ones.

参考文档

Dynamic Library Identification

Dynamic Library Standard Setup for Apps

这篇关于Mac工程动态库配置和加载探究的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1138019

相关文章

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

Zookeeper安装和配置说明

一、Zookeeper的搭建方式 Zookeeper安装方式有三种,单机模式和集群模式以及伪集群模式。 ■ 单机模式:Zookeeper只运行在一台服务器上,适合测试环境; ■ 伪集群模式:就是在一台物理机上运行多个Zookeeper 实例; ■ 集群模式:Zookeeper运行于一个集群上,适合生产环境,这个计算机集群被称为一个“集合体”(ensemble) Zookeeper通过复制来实现

CentOS7安装配置mysql5.7 tar免安装版

一、CentOS7.4系统自带mariadb # 查看系统自带的Mariadb[root@localhost~]# rpm -qa|grep mariadbmariadb-libs-5.5.44-2.el7.centos.x86_64# 卸载系统自带的Mariadb[root@localhost ~]# rpm -e --nodeps mariadb-libs-5.5.44-2.el7

hadoop开启回收站配置

开启回收站功能,可以将删除的文件在不超时的情况下,恢复原数据,起到防止误删除、备份等作用。 开启回收站功能参数说明 (1)默认值fs.trash.interval = 0,0表示禁用回收站;其他值表示设置文件的存活时间。 (2)默认值fs.trash.checkpoint.interval = 0,检查回收站的间隔时间。如果该值为0,则该值设置和fs.trash.interval的参数值相等。

NameNode内存生产配置

Hadoop2.x 系列,配置 NameNode 内存 NameNode 内存默认 2000m ,如果服务器内存 4G , NameNode 内存可以配置 3g 。在 hadoop-env.sh 文件中配置如下。 HADOOP_NAMENODE_OPTS=-Xmx3072m Hadoop3.x 系列,配置 Nam

第10章 中断和动态时钟显示

第10章 中断和动态时钟显示 从本章开始,按照书籍的划分,第10章开始就进入保护模式(Protected Mode)部分了,感觉从这里开始难度突然就增加了。 书中介绍了为什么有中断(Interrupt)的设计,中断的几种方式:外部硬件中断、内部中断和软中断。通过中断做了一个会走的时钟和屏幕上输入字符的程序。 我自己理解中断的一些作用: 为了更好的利用处理器的性能。协同快速和慢速设备一起工作

wolfSSL参数设置或配置项解释

1. wolfCrypt Only 解释:wolfCrypt是一个开源的、轻量级的、可移植的加密库,支持多种加密算法和协议。选择“wolfCrypt Only”意味着系统或应用将仅使用wolfCrypt库进行加密操作,而不依赖其他加密库。 2. DTLS Support 解释:DTLS(Datagram Transport Layer Security)是一种基于UDP的安全协议,提供类似于

动态规划---打家劫舍

题目: 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。 思路: 动态规划五部曲: 1.确定dp数组及含义 dp数组是一维数组,dp[i]代表

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影