macOS 上 Qt 应用程序的动态链接

今天,我发布了适用于配备 macOS 和 M1/M2/M3/M4 处理器(Apple Silicon)的 Apple 设备的 RaidenVideoRipper 版本。 RaidenVideoRipper 是一款快速视频编辑应用程序,可让您将视频文件的一部分剪切成新文件。您还可以制作 gif 并将音轨导出为 mp3。

接下来,我将简要描述我使用了哪些命令来完成此任务。这里发生的事情的理论,实用程序的文档,可以在以下链接中阅读:
https://www.unix.com/man-page/osx/1/otool/
https://www.unix.com/man-page/osx/1/install_name_tool/
https://llvm.org/docs/CommandGuide/llvm-nm.html
https://linux.die.net/man/1/file
https://www.unix.com/man-page/osx/8/SPCTL/
https://linux.die.net/man/1/chmod
https://linux.die.net/man/1/ls
https://man7.org/linux/man-pages/man7/xattr.7.html
https://doc.qt.io/qt-6/macos-deployment.html

首先,在 macOS 上安装 Qt,并安装 Qt 桌面开发环境。之后,例如在 Qt Creator 中组装您的项目,然后我将描述在将应用程序分发给最终用户时确保正确处理与外部动态库的依赖关系所需的内容。

在应用程序的 YOUR_APP.app/Contents 文件夹中创建 Frameworks 目录,并将外部依赖项放入其中。例如,RaidenVideoRipper 应用程序的框架如下所示:

Frameworks
├── DullahanFFmpeg.framework
│   ├── dullahan_ffmpeg.a
│   ├── libavcodec.60.dylib
│   ├── libavdevice.60.dylib
│   ├── libavfilter.9.dylib
│   ├── libavformat.60.dylib
│   ├── libavutil.58.dylib
│   ├── libpostproc.57.dylib
│   ├── libswresample.4.dylib
│   └── libswscale.7.dylib
├── QtCore.framework
│   ├── Headers -> Versions/Current/Headers
│   ├── QtCore -> Versions/Current/QtCore
│   ├── Resources -> Versions/Current/Resources
│   └── Versions
├── QtGui.framework
│   ├── Headers -> Versions/Current/Headers
│   ├── QtGui -> Versions/Current/QtGui
│   ├── Resources -> Versions/Current/Resources
│   └── Versions
├── QtMultimedia.framework
│   ├── Headers -> Versions/Current/Headers
│   ├── QtMultimedia -> Versions/Current/QtMultimedia
│   ├── Resources -> Versions/Current/Resources
│   └── Versions
├── QtMultimediaWidgets.framework
│   ├── Headers -> Versions/Current/Headers
│   ├── QtMultimediaWidgets -> Versions/Current/QtMultimediaWidgets
│   ├── Resources -> Versions/Current/Resources
│   └── Versions
├── QtNetwork.framework
│   ├── Headers -> Versions/Current/Headers
│   ├── QtNetwork -> Versions/Current/QtNetwork
│   ├── Resources -> Versions/Current/Resources
│   └── Versions
└── QtWidgets.framework
    ├── Headers -> Versions/Current/Headers
    ├── QtWidgets -> Versions/Current/QtWidgets
    ├── Resources -> Versions/Current/Resources
    └── Versions

为了简单起见,我只打印了第二层嵌套。

接下来,我们打印应用程序当前的动态依赖关系:

otool -L RaidenVideoRipper 

RaidenVideoRipper 二进制文件的输出,位于 RaidenVideoRipper.app/Contents/MacOS:

RaidenVideoRipper:
	@rpath/DullahanFFmpeg.framework/dullahan_ffmpeg.a (compatibility version 0.0.0, current version 0.0.0)
	@rpath/QtMultimediaWidgets.framework/Versions/A/QtMultimediaWidgets (compatibility version 6.0.0, current version 6.8.1)
	@rpath/QtWidgets.framework/Versions/A/QtWidgets (compatibility version 6.0.0, current version 6.8.1)
	@rpath/QtMultimedia.framework/Versions/A/QtMultimedia (compatibility version 6.0.0, current version 6.8.1)
	@rpath/QtGui.framework/Versions/A/QtGui (compatibility version 6.0.0, current version 6.8.1)
	/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit (compatibility version 45.0.0, current version 2575.20.19)
	/System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO (compatibility version 1.0.0, current version 1.0.0)
	/System/Library/Frameworks/Metal.framework/Versions/A/Metal (compatibility version 1.0.0, current version 367.4.0)
	@rpath/QtNetwork.framework/Versions/A/QtNetwork (compatibility version 6.0.0, current version 6.8.1)
	@rpath/QtCore.framework/Versions/A/QtCore (compatibility version 6.0.0, current version 6.8.1)
	/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit (compatibility version 1.0.0, current version 275.0.0)
	/System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration (compatibility version 1.0.0, current version 1.0.0)
	/System/Library/Frameworks/UniformTypeIdentifiers.framework/Versions/A/UniformTypeIdentifiers (compatibility version 1.0.0, current version 709.0.0)
	/System/Library/Frameworks/AGL.framework/Versions/A/AGL (compatibility version 1.0.0, current version 1.0.0)
	/System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL (compatibility version 1.0.0, current version 1.0.0)
	/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1800.101.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1351.0.0)

从 RaidenVideoRipper 中的 Qt 和 dullahan_ffmpeg 依赖项中可以看出。 Dullahan FFmpeg 是 FFmpeg 的一个分支,将其功能封装在动态库中,能够使用 C 例程获取当前执行进度和取消。
接下来,使用 install_name_tool 替换应用程序和所有必需库的路径。

执行此操作的命令是:

install_name_tool -change old_path new_path target

使用示例:

install_name_tool -change /usr/local/lib/libavfilter.9.dylib @rpath/DullahanFFmpeg.framework/libavfilter.9.dylib dullahan_ffmpeg.a

输入所有正确的路径后,应用程序应该正确启动。检查库的所有路径是否都是相对的,传输二进制文件,然后再次打开它。
如果您看到任何错误,请通过 otool 检查路径并通过 install_name_tool 再次更改。

当您替换的库在表中没有符号时,还会出现依赖关系混乱的错误;您可以像这样检查是否存在符号:

nm -gU path

执行后,您将看到库或应用程序的整个符号表。
也有可能您复制了错误架构的依赖项,您可以使用文件检查:

file path

文件实用程序将向您显示库或应用程序属于哪个体系结构。

此外,Qt 需要 YOUR_APP.app 目录的 Contents 文件夹中有一个 Plugins 文件夹,将插件从 Qt 复制到 Contents。接下来,检查应用程序的功能,之后您可以通过从此文件夹中删除项目并测试应用程序来开始优化插件文件夹。

macOS 安全性

复制所有依赖项并更正动态链接的路径后,您需要使用开发人员的签名对应用程序进行签名,并将应用程序版本发送给 Apple 进行公证。

如果您没有 100 美元的开发人员许可证或不想签署任何内容,请向您的用户编写有关如何启动应用程序的说明。

此指令也适用于 RaidenVideoRipper:

  • 禁用网闸:spctl –master-disable
  • 允许从“隐私与安全”中的任何来源启动:允许应用程序切换到任何地方
  • 从 zip 或 dmg 应用程序下载后删除隔离标志:xattr -d com.apple.quarantine app.dmg
  • 检查隔离标志 (com.apple.quarantine) 是否丢失:ls -l@ app.dmg
  • 如有必要,请在“隐私与安全”中添加启动应用程序的确认信息

隔离标志的错误通常会通过用户屏幕上出现的“应用程序已损坏”错误来重现。在这种情况下,您需要从元数据中删除隔离标志。

为 Apple Silicon 构建 RaidenVideoRipper 的链接:
https://github.com/demensdeum/RaidenVideoRipper/releases/download/1.0.1.0/RaidenVideoRipper-1.0.1.0.dmg

Leave a Comment

Your email address will not be published. Required fields are marked *