为什么我的产品尽量不用「外置」动态链接库

为什么我的产品尽量不用「外置」动态链接库

先说在前面,给现在或未来的用户吃颗定心丸:

文中说到的那些「因为动态库导致无法启动」的问题,

都是我在早期开发阶段踩过的坑,已经在后续版本里彻底修掉了

现在每次发版前,我都会跑一套自动脚本去检查依赖,

目的是尽可能避免类似问题再次发生。

这篇文章,更像是一个开发者自述:

我为什么在 macOS 应用里,对「外置动态链接库」这件事格外小心。


背景:一款在 Mac 上做远程的工具

我是一个 macOS 独立开发者,做了一款在 Mac 上远程连接服务器的客户端,支持:

  • • SSH
  • • RDP(远程桌面)
  • • VNC

上层界面是用苹果官方的 Swift 写的,

但底层协议实现,离不开很多 C / C++ 的第三方库。比如:

  • RDP 协议 :使用的是业界常用、非常成熟的 FreeRDP
    除了微软官方客户端之外,现在很多 RDP 工具都是在它的基础上做的,
    我选择它的原因也很简单:稳定、成熟、被验证过

对用户来说,只要「点一下就能远程上去」,

背后是 Swift + 各种第三方库协同工作。


集成第三方库,一般有两条路

把这些 C / C++ 库塞进一个 macOS 应用,常见有两种方式:

方式一:直接把源码拉进工程一起编译

优点:

  • • 想改源码就改,立刻生效
  • • 所有依赖都在工程里,比较一体化

缺点:

  • • 编译时间会变得很长
  • • 工程越来越「重」,每次改动都要等很久

方式二:先编译成静态库 / 动态库,再集成

也就是先把 C / C++ 代码编译成 .a / .dylib

主应用只负责链接这些二进制文件。

优点:

  • • 编译主应用的时间会快很多
  • • 底层库的版本比较稳定,不会经常被改动

缺点:

  • • 这些库本身可能再依赖其他库,如果不检查清楚,容易出现「隐形依赖」
  • • 一旦出了问题,排查会比有源码时麻烦一点

目前我的做法是偏向 第 2 种

把复杂的底层库整理好,再集成到主工程里。

也正是在这个过程中,我踩到了一个跟「动态库」有关的大坑。


问题出在哪:那些悄悄被依赖的动态库

预编译好的库,往往不止自己一个文件:

  • • 有的依赖静态库
  • • 有的依赖系统自带的动态库
  • • 还有的会依赖你本机通过 Homebrew 等方式安装的动态库

真正危险的是这类路径:

  • /usr/local/lib/xxxx.dylib
  • /opt/homebrew/lib/xxxx.dylib
  • • 以及类似的「只在开发机上存在」的路径

在我的开发环境里,这些库都是存在的,所以:

  • • 编译顺利
  • • 运行正常
  • • 用起来一切看似「风平浪静」

但换到用户电脑上,就不一定了:

  • • 用户通常不会在 /usr/local/lib 里装这些开发相关的动态库
  • • 应用一旦在启动时找不到依赖,就会出现 「应用无法正常启动」 的情况

换句话说:
在我这里跑得很好的版本,一旦发出去,有可能在部分用户环境下根本起不来。

这类问题,在传统桌面软件里其实不算罕见,

但在 App Store 这种面向普通用户的环境里,一旦发生就是非常糟糕的体验。


不能把希望寄托在苹果审核上

有些人可能会想:

这种问题,苹果审核应该会帮你挡住吧?

审核确实会做一些自动检测和基本验证,

但它主要是为了:

  • • 安全性
  • • 是否使用了禁止的 API
  • • 是否符合审核规则

它不会也不可能帮你覆盖所有「用户机器的环境差异」。

尤其是当你的应用已经多次通过审核之后,有时候流程会比较快,

不一定会在各种环境下完整跑一遍。

所以,对我来说结论很明确:

审核是「最后一层兜底」,
而不是替你做完所有环境测试的质量部门。

真正能放心的方式,还是要在发版前,自己多加几道保险。


我的解决方案:写脚本做一次「全面体检」

从那次之后,我给自己定了一个硬性要求:

每次发版前,都必须对打包好的 .app 做一遍「依赖体检」。

做法其实不复杂,大致分三步:

    1. 找到 .app 里所有可能会被加载的可执行文件 / 动态库
    1. 用工具(比如 otool -L)列出它们依赖了哪些动态库
    1. 把这些依赖路径和一份「允许列表」做对比,
      只要发现类似 /usr/local/lib/opt/homebrew/lib 等路径,就直接标红

跑完以后,你可以一眼看到:

  • • 哪些是系统库,没问题
  • • 哪些是你自己打包进 App 的库,也没问题
  • • 哪些是从本地环境「顺带带进来的」,必须处理

如果你自己也在做 macOS 开发,可以让 AI 帮你写一个类似的脚本,

关键词大概是:遍历 .appotool -L、过滤非系统路径 等。


这跟普通用户有什么关系?

从用户角度看,这些技术细节听起来可能有点遥远。

但最终落在体验上,其实就是两件事:

    1. 应用更稳定了
  • • 类似「因为环境差异导致无法启动」的问题,
    在发版前就会被我自己提前拦截掉。
    1. 问题越早被发现,修复越可控
  • • 比起等用户遇到问题再修,
    我更希望在你看到更新之前,就已经把这些隐患清理干净。

所以,我才会在开发流程里,多加一道这样的脚本检查。

它不会出现在界面上,但会长期影响你使用时的感觉。


为什么我对「外置动态库」格外谨慎?

总结一下我的个人原则:

  • • 能用系统自带的库,就不用要求用户自己额外安装
  • • 能静态集成进 App Bundle 的,就不要依赖用户机器上的某个路径
  • • 必须用动态库时,也要尽量做到:
  • • 要么是系统一定包含的
  • • 要么是我明确打包在 App 里的

这也是为什么我会说:

我的产品尽量不用「外置」动态链接库,

能自己带的,就尽量自己带着走。

对我来说,这不是技术洁癖,

而是「减少你遇到意外的几率」的一种选择。


写在最后

这篇文章更多是给对技术实现好奇的朋友看的一个幕后故事:

  • • 我在集成 RDP 等底层能力时,踩过哪些坑
  • • 为什么在动态库这件事上会格外慎重
  • • 以及我后来是怎么把它变成一个固定的「质量检查环节」

作为用户,你不需要记住这些工具名,也不需要会写脚本。

你只要知道:

当你看到一个小版本更新时,

很多看不到的地方,其实都在一点点变得更稳、更可控。

这就是我现在开发和发版时,一直坚持的一件小事。

相关推荐
Healer91839 分钟前
Promise限制重复请求
前端
梵得儿SHI39 分钟前
Vue 响应式原理深度解析:Vue2 vs Vue3 核心差异 + ref/reactive 实战指南
前端·javascript·vue.js·proxy·vue响应式系统原理·ref与reactive·vue响应式实践方案
零日失眠者40 分钟前
【系统监控系列】005:CPU温度监控脚本
后端·python
chenyunjie41 分钟前
我做了一个编辑国际化i18n json文件的命令行工具
前端
踏浪无痕42 分钟前
Maven 依赖拉不下来?一文终结所有坑点
spring boot·后端·面试
踏浪无痕44 分钟前
你真的懂泛型吗?手写 MyBatis-Plus + Jackson,揭秘框架设计的精髓
后端·json·mybatis
Emma歌小白1 小时前
移除视觉对象里“行的型号”造成的行级筛选,但不移除用户的 slicer 筛选
前端
茶杯6751 小时前
“舒欣双免“方案助力MSI-H/dMMR结肠癌治疗新突破
java·服务器·前端
昔人'1 小时前
css `svh` 单位
前端·css