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

news2024/11/16 2:47:58

缘起

最近在做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
fi

cp  ${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 72
    name @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_DYLIB
          cmd LC_ID_DYLIB
      cmdsize 52
         name @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_RPATH
          cmd LC_RPATH
      cmdsize 32
         path /usr/lib/swift …
--
          cmd LC_RPATH
      cmdsize 40
         path @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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2105768.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FastDFS分布式存储:概念、集群案例

FastDFS FastDFS:Fast DistributedFileSystem,快速分布式文件系统 FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等&…

在x86上拉取ARM架构的镜像

添加–platform 参数 docker pull --platform linux/arm64 nginx:1.27.1查看镜像架构 docker inspect nginx:1.27.1 | grep Architecture

智慧社区管理系统平台:架构全新升级,Java商业版OEM开源定制开发

智慧社区综合管理平台,致力于打造以党建为引领,精细化治理 个性化服务于一体的智慧社区平台。 平台整体包含智慧社区综合管理云平台数字孪生大屏可视化APP微信小程序,满足智慧街道、智慧社区标准化功能建设。数字孪生倾斜摄影,支…

云计算之网络

目录 一、VPC:云网络的基石 1.1 VPC产品介绍 1.2 vswitch交换机 1.3 vrouter路由器 1.4 产品架构 1.5 常见问题解答及处理 1.5.1 VPC内如何查询某个IP归属? 1.5.2 网络ACL阻断导致ECS访问CLB不通 1.5.3 EIP秒级突发/分布式限速丢包 1.5.4 NAT网关的流量监…

C# 窗体小实验 点击确定按钮返回文本框显示

1.1创建c项目 1.2配置新项目 1.3打开工具箱 1.4 创建按钮和文本框 拖至到窗体中 右键确定按钮 点击属性 设置Text(确定)文本显示 ,buttton2同理 设置退出 设置完成效果 双击确定按钮 进入编辑代码窗口 编写代码如下: 然后设置退出的 代码: 单…

btrace 开源!基于 Systrace 高性能 Trace 工具

android.os.Trace#beginSection 会调用 nativeTraceBegin 方法,该方法实现参考 frameworks/base/core/jni/android_os_Trace.cpp。 static void android_os_Trace_nativeTraceBegin(JNIEnv* env, jclass, jlong tag, jstring nameStr) { withString(env, nameStr…

实验报告: lookie-lookie 项目测试与分析

目录 一、实验目的 二、实验环境 三、实验步骤 1. 下载与准备项目 1.1 从 GitHub 获取项目 1.2 查看项目文件结构 2. 运行项目 2.1 启动项目 2.2 浏览器设置 3. 项目体验 3.1 功能测试 3.2 运行截图 4. 文件结构分析 4.1 总体结构 4.2 主要文件和目录说明 5. 数…

ElasticSearch分布式搜索引擎入门

一、ElasticSearch Elasticsearch是一个基于 Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布, 是一种流行的企业级搜索引擎。…

图像白平衡

目录 效果 背景 什么是白平衡? 实现原理 将指定图色调调整为参考图色调主要流程 示例代码 效果 将图一效果转换为图二效果色调: 调整后,可实现色调对换 背景 现有两张图像,色调不一致,对于模型重建会有影响。因…

海洋运输船5G智能工厂物联数字孪生平台,推进制造业数字化转型

海洋运输船5G智能工厂物联数字孪生平台,推进制造业数字化转型。在当今全球制造业的浪潮中,数字化转型已成为不可逆转的趋势,它不仅重塑了生产流程,更深刻影响着企业的竞争力与可持续发展能力。其中,海洋运输船5G智能工…

基于python的Selenium webdriver环境搭建(笔记)

一、PyCharm安装配置Selenium环境 本文使用环境:windows11、Python 3.8.1、PyCharm 2019.3.3、Selenium 3.141.0 测试开发环境搭建综述 安装python和pycharm安装浏览器安装selenium安装浏览器驱动测试环境是否正确 这里我们直接从第三步开始 1.1 Seleium安装…

QT实现文本的读写

使用QT读写文件 来,在程序中文件的读写是非常重要的,毕竟我们在大多数时候都是要访问文本文件的,那么今天就来学习一下怎么使用QT来读写文件。 1.写界面 直接在ui界面中编辑即可 布局小技巧我们先选择两个按钮,然后水平布局&am…

iOS18 beta版本怎么回退至iOS17正式版本?

截止目前,苹果最近的iOS18的beta测试版本已经发了8版了,有许多朋友们都已升级提前尝鲜了,升级体验后许多果粉朋友们觉得有许多功能还是不够稳定,有些许bug,就想要降级,回退到iOS17的正式版,但又…

deep-live-cam实时换中文整合包下载,双击exe直接运行

windows环境整合包下载地址: 点击下载 直接解压,双击启动.exe即可使用 硬件要求:有英伟达显卡,且要支持CUDA 硬件不符合要求也不用急,软件也有对应mac版本和windows非N卡版本,我还没做成整合包,…

2. GIS数据工程师岗位职责、技术要求和常见面试题

本系列文章目录: 1. GIS开发工程师岗位职责、技术要求和常见面试题 2. GIS数据工程师岗位职责、技术要求和常见面试题 3. GIS后端工程师岗位职责、技术要求和常见面试题 4. GIS前端工程师岗位职责、技术要求和常见面试题 5. GIS工程师岗位职责、技术要求和常见面试…

沐风老师3DMax地形拟合插件使用方法详解

3DMax地形拟合插件使用教程 3DMax地形拟合插件,只需单击几下鼠标,即可将地形表面与道路对齐。它很容易使用。 (注意:如果不仔细阅读,会误认为是这是一个道路拟合(投影)到地形的插件&#xff0c…

HarmonyOS开发实战( Beta5版)高负载组件的渲染实践规范

简介 在应用开发中,有的页面需要在列表中加载大量的数据,就会导致组件数量较多或者嵌套层级较深,从而引起组件负载加重,绘制耗时增长。虽然可以通过组件复用避免组件重复创建,但是如果每个列表项中包含的组件较多&…

C/C++:函数指针

欢迎来到 破晓的历程的 博客 ⛺️不负时光,不负己✈️ 文章目录 引言函数指针的概念函数指针的实现C语言实现C实现 函数指针的应用 引言 我们之前学过各种各样指针,今天我们来讨论一下函数指针 我们先分析C和C不同定义函数指针的方式,然后进…

uniapp去除顶部标题栏

相信很多同学和我一样,刚学uniapp的时候想去除自带的这个标题栏不知道如何去除🤪 其实很简单,只需两个步骤即可彻底除掉,首先找到项目文件夹下的pages.json路由文件点开,在这个文件里可以看到你创建的所有页面&#x…

git修改提交名字

大家在使用git的时候,有的时候可能不是使用自己的账号,或者说账号的信息不符合自己的预期,具体表现在什么地方呢?在提交代码的时候,名字不是自己的,或者是名字不是自己想要的。 下面就是如何查看和修改。 …