今天和同事讨论到一个问题:
bundle
和动态库一样吗?
同事说 bundle
只是包含了其他资源而已,其实就是动态库。
我看 Mach-O 文件类型里 MH_BUNDLE
与 MH_DYLIB
是分开的,所以觉得 .bundle
里面的 Mach-O 文件和 dylib
的 Mach-O 文件应该会有些不一样。不过我也不知道有什么不一样,所以学习了一下,以此文记之。
定义一下动态库为 dylib
Mach-O 文件, bundle
指的是 .bundle
文件夹里面的 Mach-O 文件,一般类 Unix 系统叫做 .so
库,不过苹果官方建议叫做 .bundle
。
P.S. 这里苹果官方不厚道,它推荐用 .bundle
作为 MH_BUNDLE
类型文件的后缀名但不强制,然后自己还把 .bundle
后缀名用作一个类似 .app
的资源与可执行文件打包。所以很容易就会混淆两个概念。实际上我看到的 MH_BUNDLE
类型的 Mach-O 基本上都没有后缀名,有 .bundle
后缀名的基本上都是资源与可执行文件的打包。
先说结论: 通常语境下 bundle
和 dylib
没有区别。要较真的话也只有在 OS X 10.5 以前才有比较大的区别,所以同事说 bundle
和动态库没有区别是对的。
P.S. ELF 系统(Executable and Linking Format,Unix-like 系统基本都是)上这两者完全相等,只有 Mac 的 dyld 对他们做了点区别对待。
一、File Type 不同
Mach-O 文件的 header 里有一个 type
字段表示当前文件的类型,如果把 .bundle
文件夹解开,里面的 Mach-O 文件的类型是 MH_BUNDLE
,而 dylib
则是 MH_DYLIB
。
➜ otool -hv AppKit
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
MH_MAGIC_64 X86_64 ALL 0x00 DYLIB 60 8344 NOUNDEFS DYLDLINK TWOLEVEL APP_EXTENSION_SAFE
➜ AppKit.framework otool -hv /System/Library/Audio/Plug-Ins/HAL/AirPlay.driver/Contents/MacOS/AirPlay
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
MH_MAGIC_64 X86_64 ALL 0x00 BUNDLE 21 2544 NOUNDEFS DYLDLINK TWOLEVEL
二、加载、链接时机不同
在 macOS 上,动态加载通过 dyld
进行。bundle
和 dylib
两种文件都可以使用 dlopen
加载。两者的区别要在 dyld
的源码里面找。
dyld
的 dlopen()
实现主要关注是这几个地方:
dlopen()
load()
loadPhase0()
loadPhase1()
loadPhase2()
loadPhase3()
loadPhase4()
loadPhase5()
loadPhase6()
checkandAddImage()
- 如果是
dylib
就从sAllImages
找到一样路径的 image 先删掉 dylib
和bundle
能使用的 API 不一样,所以这里还得判断context.mustBeBundle
和isBundle()
是否匹配
// some API's restrict what they can load if ( context.mustBeBundle && !image->isBundle() ) throw "not a bundle"; if ( context.mustBeDylib && !image->isDylib() ) throw "not a dylib";
- 如果是
bundle
就不会加到 global list,因为bundle
可以只加载但不链接。
- 如果是
所以结论是 bundle
可以只加载不链接,而 dylib
加载后就链接了。
三、NSObjectFileImage
只有 bundle
能用
dyld
提供了 NSObjectFileImage
接口,这些接口只有 bundle
能用,只加载不链接就通过这个接口来实现。
NSObjectFileImageReturnCode NSCreateObjectFileImageFromFile(const char* pathName, NSObjectFileImage *objectFileImage)
里面会调用 load()
方法加载 bundle
,这类接口的 context.mustBeBundle
为 true
,底下判断的时候遇到非 bundle
就会报错。
load()
之后再使用以下方法链接:
NSModule NSLinkModule(NSObjectFileImage objectFileImage, const char* moduleName, uint32_t options)
四、结论
NSObjectFileImage
相关的接口从 OS X 10.5 开始已经被废弃了。
在 Mac OS X 10.5 (2007 年) 以前,bundle
可以被 unload 但是 dylib
不可以,10.5 开始 dylib
也可以被 unload 了。dlclose()
的实现很简单,调用时减一下引用计数,为 0
就从走垃圾回收接口 garbageCollectImages()
删掉。
经过以上调查,现如今的 bundle
跟 dylib
在使用上几乎可以完全对等。要说区别那就只有编译 dylib
为 shared library 的时候需要加上版本号,而 bundle
只会给自己的 App 用就没有必要了。
libbz2.1.0.5.tbd
libbz2.1.0.tbd
libbz2.tbd
至于 Mach-O Header file type 的区别,只是给 dyld
作 NSObjectFileImage
接口判断而已,这些接口废弃了那自然就没有区别了。