
今天和同事讨论到一个问题:
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 接口判断而已,这些接口废弃了那自然就没有区别了。