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

相关文章

VScode连接远程Linux服务器环境配置图文教程

《VScode连接远程Linux服务器环境配置图文教程》:本文主要介绍如何安装和配置VSCode,包括安装步骤、环境配置(如汉化包、远程SSH连接)、语言包安装(如C/C++插件)等,文中给出了详... 目录一、安装vscode二、环境配置1.中文汉化包2.安装remote-ssh,用于远程连接2.1安装2

SpringBoot实现动态插拔的AOP的完整案例

《SpringBoot实现动态插拔的AOP的完整案例》在现代软件开发中,面向切面编程(AOP)是一种非常重要的技术,能够有效实现日志记录、安全控制、性能监控等横切关注点的分离,在传统的AOP实现中,切... 目录引言一、AOP 概述1.1 什么是 AOP1.2 AOP 的典型应用场景1.3 为什么需要动态插

Redis多种内存淘汰策略及配置技巧分享

《Redis多种内存淘汰策略及配置技巧分享》本文介绍了Redis内存满时的淘汰机制,包括内存淘汰机制的概念,Redis提供的8种淘汰策略(如noeviction、volatile-lru等)及其适用场... 目录前言一、什么是 Redis 的内存淘汰机制?二、Redis 内存淘汰策略1. pythonnoe

windos server2022的配置故障转移服务的图文教程

《windosserver2022的配置故障转移服务的图文教程》本文主要介绍了windosserver2022的配置故障转移服务的图文教程,以确保服务和应用程序的连续性和可用性,文中通过图文介绍的非... 目录准备环境:步骤故障转移群集是 Windows Server 2022 中提供的一种功能,用于在多个

windos server2022里的DFS配置的实现

《windosserver2022里的DFS配置的实现》DFS是WindowsServer操作系统提供的一种功能,用于在多台服务器上集中管理共享文件夹和文件的分布式存储解决方案,本文就来介绍一下wi... 目录什么是DFS?优势:应用场景:DFS配置步骤什么是DFS?DFS指的是分布式文件系统(Distr

mac中资源库在哪? macOS资源库文件夹详解

《mac中资源库在哪?macOS资源库文件夹详解》经常使用Mac电脑的用户会发现,找不到Mac电脑的资源库,我们怎么打开资源库并使用呢?下面我们就来看看macOS资源库文件夹详解... 在 MACOS 系统中,「资源库」文件夹是用来存放操作系统和 App 设置的核心位置。虽然平时我们很少直接跟它打交道,但了

关于Maven中pom.xml文件配置详解

《关于Maven中pom.xml文件配置详解》pom.xml是Maven项目的核心配置文件,它描述了项目的结构、依赖关系、构建配置等信息,通过合理配置pom.xml,可以提高项目的可维护性和构建效率... 目录1. POM文件的基本结构1.1 项目基本信息2. 项目属性2.1 引用属性3. 项目依赖4. 构

龙蜥操作系统Anolis OS-23.x安装配置图解教程(保姆级)

《龙蜥操作系统AnolisOS-23.x安装配置图解教程(保姆级)》:本文主要介绍了安装和配置AnolisOS23.2系统,包括分区、软件选择、设置root密码、网络配置、主机名设置和禁用SELinux的步骤,详细内容请阅读本文,希望能对你有所帮助... ‌AnolisOS‌是由阿里云推出的开源操作系统,旨

mysql-8.0.30压缩包版安装和配置MySQL环境过程

《mysql-8.0.30压缩包版安装和配置MySQL环境过程》该文章介绍了如何在Windows系统中下载、安装和配置MySQL数据库,包括下载地址、解压文件、创建和配置my.ini文件、设置环境变量... 目录压缩包安装配置下载配置环境变量下载和初始化总结压缩包安装配置下载下载地址:https://d

SpringBoot项目启动后自动加载系统配置的多种实现方式

《SpringBoot项目启动后自动加载系统配置的多种实现方式》:本文主要介绍SpringBoot项目启动后自动加载系统配置的多种实现方式,并通过代码示例讲解的非常详细,对大家的学习或工作有一定的... 目录1. 使用 CommandLineRunner实现方式:2. 使用 ApplicationRunne