Fink

移植 - 2. 共享代码

2.1 共享库和可加载模块对比

Mach-O 的一个令人吃惊的特性是对共享函数库和可加载模块的严格区分。 在 ELF 系统上,两者是相同的;任何的共享代码都可以作为函数库或作为动态加载模块。 Use otool -hv some_file to see the filetype of some_file.

Mach-O 共享函数库具有 MH_DYLIB 文件类型,扩展名为 .dylib。 它们可以通过常用的静态连接标志进行连接,,比如 -lfoo 会连接 libfoo.dylib。 不过,它们不能以模块形式加载。 (附注:共享库可以通过一个 API 动态装载。 不过,那个 API 和一般用于束的 API 并不相同;语法上的差别也使它不能作为 dlopen() 的模拟。 最值得注意的是,共享库不能被卸载。)

按 Mach-O 的术语,可加载模块称为束("bundles")。 它们的文件类型为 MH_BUNDLE。 由于没有其它的组件会使用它,它们可以有任意的扩展名。 苹果推荐使用 .bundle 扩展名,但多数移植的软件会因为兼容性的原因而使用 .so 的扩展名。 束可以通过 dyld API 动态加载和卸载,在这些 API 之上有一个封装来模拟 dlopen()。 没有办法把束当作共享库来进行连接。 不过,可以把一个束连接到一个真正的共享库这样在束加载的时候,共享库也会被自动加载。

2.2 版本编号

在 ELF 系统中,版本号通常会添加到共享库的文件名后面,例如:libqt.so.2.3.0。 在 Darwin 中,版本号被放在函数库名和扩展名之间,例如:libqt.2.3.0.dylib。 注意,这样使得你可以在连接的时候指定所要连接的版本,比方说对上面的例子,可以使用:-lqt.2.3.0

当创建一个共享库时,你可以指定一个在运行时搜索函数库所用的名字。 通常都会这样去做,而使得可以同时安装一个函数库的不同主版本。 在 ELF 系统上,这称为 soname。 区别是在 Darwin 上你可以(而且是必须)指定包括文件名的完整路径。 这消除了 "rpath" 选项以及 ldconfig/ld.so.cache 系统的需要。 要使用一个还没有安装的函数库,你可以设置 DYLD_LIBRARY_PATH 环境变量;查阅 dyld 的手册页获取更多细节。

Mach-O 的格式还提供了真正的次要版本号的检查,在 ELF 系统上这点不是很清楚。 每个 Mach-O 函数库有两个版本号:"当前" 版本号和 "兼容" 版本号。 这两个版本号都有三组以句点分隔的数字组成,例如:1.4.2。 第一个数字必需为非零值。 第二和第三个数字可以不提供,这时缺省为零值。 没有指定版本号的时候,默认为 0.0.0,这是一种通配值。

The "current" version is for informational purposes only. The "compatibility" version is used for checking as follows. When an executable is linked, the version information from the library is copied into the executable. When that executable is run, the stored version information is checked against the version information in the library that is loaded. dyld generates a run-time error and terminates the program unless the loaded library version is equal to or newer than the one used during linking.

2.3 编译器标志

在 Darwin 上默认会生成"位置无关代码"(PIC)。 事实上,PowerPC 的代码在设计上就是位置无关的,所以这不会产生性能的损失。 因此,在编译共享库或模块的时候,你不需要指定 PIC 选项。 不过,连接器不允许在共享库中使用 "common" 标志,所以你需要使用 -fno-common 编译器选项。

2.4 构建一个共享库

要构建一个共享库,你要使用 -dynamiclib 选项调用编译器。 这可以通过一个完整的例子来演示。 我们要编译一个名为 libfoo 的库,它由 source.c 和 code.c 两个源程序文件组成。 版本号是 2.4.5,2 是主版本号(发生了不兼容的 API 改变),4 是次要版本号(后向兼容的 API 改变),5 是修正错误的修订版计数(有些人把这称为 "teeny" 修订版,它包含完全兼容的改变)。 函数库不依赖于其它共享库,会被安装在 /usr/local/lib

cc -fno-common -c source.c
cc -fno-common -c code.c
cc -dynamiclib -install_name /usr/local/lib/libfoo.2.dylib \
 -compatibility_version 2.4 -current_version 2.4.5 \
 -o libfoo.2.4.5.dylib source.o code.o
rm -f libfoo.2.dylib libfoo.dylib
ln -s libfoo.2.4.5.dylib libfoo.2.dylib
ln -s libfoo.2.4.5.dylib libfoo.dylib

注意版本号的各个部分所使用的地方。 When linking against this library, one would normally use the -lfoo flag, which accesses the libfoo.dylib symlink. Regardless of which symlink or file is specified, though, it is the install_name that is written into one's program. That means one can delete the "higher" (less version-specific) symlink libfoo.dylib after compiling. During a minor-revision library upgrade, one just changes the target of the libfoo.2.dylib symlink that is used by the runtime dynamic linker.

2.5 构建一个模块

要构建一个可加载模块,你用 -bundle 参数调用编译器。 如果模块会使用主程序中的标识符,你需要指定 -undefined suppress 来允许未定义标识符, 同时也配合使用 -flat_namespace 来满足 Mac OS X 10.1 中的新连接器。 一个详细的例子是:

cc -fno-common -c source.c
cc -fno-common -c code.c
cc -bundle -flat_namespace -undefined suppress \
 -o mymodule.so source.o code.o

注意这里没有使用版本号码。 理论上来说,可以使用版本号码,但从实用的角度来说,这样做是没有意义的。 另外注意的是,对束没有命名限制。 一些软件包喜欢使用 "lib",因为在其它系统会有这个要求。这样做不会有问题 since a program would use the full filename when loading a module.

Next: 3. GNU libtool