【2017-11-18】【转】使用 GNU Libtool 创建库
[历史归档]本文原发布于 cstriker1407.info 个人博客内容为历史存档仅供参考。发布时间2017-11-18 标题【转】使用 GNU Libtool 创建库分类编程【转】使用 GNU Libtool 创建库Libtool 是一个工具Libtool 基本用法创建 Libtool 对象文件建立 Libtool 对象文件建立 Libtool 库安装 Libtool 库使用 Libtool 库卸载 Libtool 库Libtool 高级用法创建可动态加载模块禁止创建动态或者静态链接库Whether or not to build shared libraries.Libtool 命令模式编译模式安装模式卸载模式执行模式清除模式库版本信息库接口库版本号Libtool 库版本号结合 Autoconf 和 Automake 使用 Libtool初始话 Automake这是在 Autoconf 中使用 Libtool 唯一必须的宏告诉 Autoconf 通过 Makefile.in 自动生成 Makefile\_LTLIBRARIES 是 Automake 支持 Libtool 的原语可以通过 \_LDFLAGS 传递选项给 Libtool通过 \_LIBADD 可以指定库依赖关系小结本文转自【 https://www.ibm.com/developerworks/cn/aix/library/1007_wuxh_libtool/ 】在不同的系统中建立动态链接库的方法有很大的差别这主要是因为每个系统对动态链接库的看法和实现并不相同以及编译器对动态链接库支持的选项也不太一样。对于开发人员如果尝试将使用动态库的软件在这些系统之间移植需要参考枯涩难懂的系统手册以及修改相应的 Makefile这一工作是乏味的并且具有一定的难度。使用 GNU Libtool 可以容易的在不同的系统中建立动态链接库。它通过一个称为 Libtool 库的抽象隐藏了不同系统之间的差异给开发人员提供了一致的的接口。对于大部分情况开发人员甚至不用去查看相应的系统手册只需要掌握 GNU Libtool 的用法就可以了。并且使用 Libtool 的 Makefile 也只需要编写一次就可以在多个系统上使用。Libtool 库可以是一个静态链接库可以是一个动态链接库也可以同时包含两者。在这篇文档中我们围绕 Libtool 库的建立和使用只是在适当的说明 Libtool 库和系统动态或者静态链接库之间的映射关系。Libtool 是一个工具虽然 Libtool 隐藏了在不同平台创建链接库的复杂性但其最终还是需要底层系统对链接库的支持它不能超越系统的限制例如Libtool 并不能在不支持动态链接库的系统中创建出动态链接库。Libtool 基本用法这一节以实例来说明如何使用 Libtool 从源代码创建最终链接库以及执行程序的完整步骤这是软件开发过程中经常使用的内容包括 :创建 Libtool 对象文件首先需要准备一个源文件 compress.c代码如下#includesys/mman.h#includesys/stat.h#includefcntl.h#includestdio.h#includestdlib.h#includeunistd.h#includestring.h#includelimits.h#includeassert.h#includezlib.h/* 一个简单的文件压缩函数 */intcompress_file(constchar*filename){intsrc_fd,dest_fd;structstatsb;Bytef*src,*dest;uLong dest_len;chardest_file[PATH_MAX];src_fdopen(filename,O_RDONLY);assert(dest_fd!-1);assert(fstat(src_fd,sb)!-1);srcmmap(NULL,sb.st_size,PROT_READ,MAP_PRIVATE,src_fd,0);assert(src!MAP_FAILED);dest_lencompressBound(sb.st_size);destmalloc(dest_len);assert(dest);assert(compress(dest,dest_len,src,sb.st_size)Z_OK);munmap(src,sb.st_size);close(src_fd);snprintf(dest_file,sizeof(dest_file),%s.z,filename);dest_fdcreat(dest_file,S_IRUSR|S_IWUSR);assert(dest_fd!-1);write(dest_fd,dest,dest_len);close(dest_fd);free(dest);return0;}这个文件实现了一个函数 compress_file()它接收一个文件名作为参数然后对文件进行压缩生成一个 .z结尾的压缩文件。在这个文件中使用了 compress()函数这个函数是有由 libz 提供的。从源文件建立 Libtool 库需要经过两个步骤先建立 Libtool 对象文件再建立 Libtool 库。建立 Libtool 对象文件如果使用传统的方式建立对象文件通常使用下面的命令:$gcc -c compress.c使用 Libtool 则使用下面的命令:$libtool --modecompile gcc -c foo.c可以看到使用 Libtool 只需要将“传统”的命令 (gcc -c foo.c) 作为参数传递给 Libtool 即可。在上面的命令中libtool 使用 compile模式 (–modecompile 选项 )这是建立对象文件的模式Libtool 还有其它的模式后面将介绍。上面的命令输出如下:mkdir .libsgcc -c compress.c-fPIC -DPIC-o .libs/compress.ogcc -c compress.c -o compress.o /dev/null 21它建立了两个文件一个是 .libs/compress.o在建立这个文件时Libtool 自动插入了 -fPIC和 -DPIC选项告诉编译器生成位置独立的代码之后将用这个文件来建立动态链接库。生成第二个文件 compress.o没有添加额外的选项它准备用来建立静态链接库。除了上面的两个文件之外Libtool 还建立了一个文件 compress.lo这个文件就是 Libtool 对象文件实际上也就是一个文本文件里面记录了建立动态链接库和静态链接库分别所需要的真实文件名称后面 Libtool 将使用这个文件而不是直接的使用 .libs/compress.o 和 compress.o。建立 Libtool 库用下面的命令建立 Libtool 库 :$libtool–modelinkgcc -o libcompress.la compress.lo -rpath /tmp -lz注意这里使用compress.lo 作为输入文件并且告诉 Libtool生成的目标文件为 libcompress.la.la 是 Libtool 的库文件后缀。-rpath选项告诉 Libtool 这个库将被安装到什么地方如果省略了 -rpath选项那么不会生成动态链接库。因为我们的库中使用了 libz 提供的 compress 函数所以也提供了 -lz 选项Libtool 会记住这个依赖关系后续在使用我们的库时自动的将依赖的库链接进来。上面的命令输出如下 :gcc-shared.libs/compress.o-lz-Wl,-soname -Wl,libcompress.so.0-o.libs/libcompress.so.0.0.0(cd .libsrm-flibcompress.so.0ln-slibcompress.so.0.0.0 libcompress.so.0)(cd .libsrm-flibcompress.soln-slibcompress.so.0.0.0 libcompress.so)ar cru .libs/libcompress.a compress.o ranlib .libs/libcompress.a creating libcompress.la(cd .libsrm-flibcompress.laln-s../libcompress.la libcompress.la)可以看到Libtool 自动的插入了建立动态链接库需要的编译选项 -shared。并且它也建立了静态链接库 .libs/libcompress.a后面我们将会介绍如何控制 Libtool 只建立需要的库。你可能会奇怪为什么建立的动态链接库有 .0 和 .0.0.0 这样的后缀这里先不用理会它后面在介绍 Libtool 库版本信息时将会解释这点。值得注意的是Libtool 希望后续使用 libcompress.la 文件而不是直接使用 libcompress.a 和 libcompress.so 文件如果你这样做虽然可以但会破坏 Libtool 库的可移植性。安装 Libtool 库如果打算发布建立好的 Libtool 库可以使用下面的命令安装它 :$libtool --modeinstall install -c libcompress.la /tmp我们需要告诉 Libtool 使用的安装命令Libtool 支持 install 和 cp这里使用的是 install。虽然前面我们在建立库时通过 -rpath 选项指定了库准备安装的路径 (/tmp)但是这里我们还得要提供安装路径。请确保它们一致。这个命令的输出如下 :install.libs/libcompress.so.0.0.0 /tmp/libcompress.so.0.0.0(cd /tmp{ln-s-flibcompress.so.0.0.0 libcompress.so.0||{rm-flibcompress.so.0ln-slibcompress.so.0.0.0 libcompress.so.0;};})(cd /tmp{ln-s-flibcompress.so.0.0.0 libcompress.so||{rm-flibcompress.soln-slibcompress.so.0.0.0 libcompress.so;};})install.libs/libcompress.lai /tmp/libcompress.lainstall.libs/libcompress.a /tmp/libcompress.achmod644/tmp/libcompress.a ranlib /tmp/libcompress.a可以看到它安装了真实的动态链接库和静态链接库同时也安装了 Libtool 库文件 libcompress.la这个文件可以被后续的 Libtool 命令使用。在安装完成之后可能还需要做一些配置才能正确使用Libtool 的 finish 模式可以在这方面给我们一些提示 :libtool -n --modefinish /tmp这个命令的输出有点长所以不在这里列出如果不能正常的使用安装好的库请运行这个命令。使用 Libtool 库要在应用程序中使用前面创建的 Libtool 库很简单准备一个源文件 main.c它将使用 libcompress.la 库中定义的函数代码如下 :#includestdio.hexternintcompress_file(constchar*filename);intmain(intargc,char*argv[]){if(argc2){printf(usage:%s file ,argv[0]);return1;}returncompress_file(argv[1]);}我们还是要先为 main.c 建立 Libtool 对象文件这和前面的方法一样 :$libtool --modecompile gcc -c main.c使用安装的库使用下面的命令链接执行文件 :$libtool --modelink gcc -o main main.lo /tmp/libcompress.la我们也可以直接使用 libcompress.a 或者 libcompress.so但是使用 Libtool 更加简单因为它会将帮助你解决依赖关系例如我们的 libcompress 依赖 libz。上面命令的输出如下 :gcc -o main .libs/main.o /tmp/libcompress.so -lz-Wl,–rpath -Wl,/tmp -Wl,–rpath -Wl,/tmp这里Libtool 自动选择链接动态链接库并且加上了运行时需要的 --rpath 选项以及依赖的库 -lz。如果要使用静态链接库只需要加上 -static-libtool-libs选项即可如下 :$libtool --modelink gcc -o main main.lo /tmp/libcompress.la -static-libtool-libs这个命令的输出如下 :gcc -o main .libs/main.o /tmp/libcompress.a -lz使用未安装的库也可以使用还没有安装的库这和使用安装好的库几乎相同只是指定的输入文件位置不一样假如我们在同一个目录中开发 compress.c 和 main.c那么使用下面的命令 :$ libtool --modelink gcc -o main main.lo ./libcompress.la和使用安装的库不一样这个时候建立的 main 程序只是一个封装脚本如果你直接执行它不会有什么问题但是如果你想调试它例如 :$ gdb maingdb 会报怨 main 不是可执行格式不能接受。这个时候我们需要使用 Libtool 的执行模式使用下面的命令调试程序 :$libtool --modeexecute gdb main卸载 Libtool 库使用下面的命令可以卸载安装的库 :$libtool --modeuninstall rm /tmp/libcompress.la这个命令的输出如下 :rm /tmp/libcompress.la /tmp/libcompress.so.0.0.0 /tmp/libcompress.so.0/tmp/libcompress.so /tmp/libcompress.a这将删除所有安装的库文件。Libtool 高级用法这一节将对 Libtool 进行更加全面的描述包括下面的内容 :创建可动态加载模块有些高级的软件系统在建立时不需要与特定的库链接而在运行时可以动态加载符合规范的库来提供额外的功能这通常称为插件系统。可动态加载的库和通常的库有一些区别它们可以通过 dlopen() 打开并且可以通过 dlsym() 查询它输出的符号。使用 Libtool 可以很容易的建立这样的库还是以前面的 compress.c 为例我们可以通过这样的命令建立一个可动态加载模块 :$ libtool --modelink gcc -o compress.la compress.lo -rpath /tmp-lz -module -avoid-version这里添加了额外的两个参数-module告诉 Libtool 建立一个可动态加载的模块-avoid-version告诉 Libtool 不要添加版本号。在 UNIX/Linux 系统中库通常以 lib 作为前缀可在上面我们指定的输出文件为 compress.la而不是 libcompress.la这也是可动态加载模块的一个特征它不需要遵循通常的库命名规则。在实际应用中可动态加载模块通常会使用主程序提供的一些函数为了满足动态模块的需求在编译主程序时需要添加 -export-dynamic选项。禁止创建动态或者静态链接库大部分情况下Libtool 都配置成同时创建动态链接库和静态链接库。可以通过下面的命令查看 Libtool 的当前配置 :$libtool – features它的输出如下 :host: i686-pc-linux-gnuenable shared librariesenable static libraries可是有时侯只想创建动态链接库或者只想创建静态链接库这需要修改 Libtool 自身。后面介绍 Libtool 结合 Autoconf 和 Automake 使用时将有更加简单的办法。禁止创建动态链接库Libtool 自身是一个安装在 /usr/bin 目录下的 Shell 脚本为了修改它我们需要将它复制到一个有修改权限的目录然后找到下面的两行 :Whether or not to build shared libraries.build_libtool_libsyes将 yes 改为 no。之后用这个修改过的 libtool 就不会创建动态链接库。还有其它几个方法可以禁止创建动态链接库第一个方法是在链接时不提供 -rpath 选项第二个方法是在链接时使用 -all-static选项第三个方法是指定目标文件为 libcompress.a 而不是 libcompress.la。禁止创建静态链接库在 Libtool 脚本中找到下面两行55 # Whether or not to build static libraries.56 build_old_libsno将 yes 改为 no。之后用这个修改过的 libtool 就不会创建静态链接库。Libtool 命令模式在前面我们已经用到了 Libtool 的大部分命令模式每个命令模式用于不同的阶段Libtool 根据当前的命令模式添加需要的编译器选项。Libtool 支持下面的几个命令模式 :编译模式 ;连接模式 ;安装模式 ;完成模式 ;卸载模式 ;执行模式 ;清除模式 ;每个命令模式对应开发中的不同阶段但并不是在每个项目中都需要使用上面所有的模式例如一个不需要安装的库就不需要安装和卸载模式。编译模式编译模式用于建立从源文件建立对象文件。它需要一个编译器名称作为第一个参数并且还要提供 -c 选项Libtool 将根据源文件名称自动选择目标文件名称。如果是建立动态链接库它也会加入相应的选项例如 -fPIC等等。编译模式使用示例 :$ libtool --modecompile gcc -c src.c链接模式链接模式用于建立 Libtool 库或者可执行文件如果输出文件名称以 .la 结尾那么它将尝试建立 Libtool 库。如果输出文件名称以 .a 结尾它就只建立静态链接库。如果输出文件名称以 .lo 或者 .o 结尾则建立一个可重新加载的对象文件这经常叫做部分链接。否则它就建立一个执行文件。链接模式使用示例 :$ libtool --modelink gcc -o library.la src.lo -rpath /usr/local/lib安装模式安装模式用于安装 Libtool 库或者执行程序。它的第一个参数必须是 install 或者 cp之后是要安装的文件以及目标路径。安装模式使用示例 :$ libtool --modeinstall install -c library.la /usr/local/lib完成模式完成模式是在安装完 Libtool 库之后在使用之前进行适当的配置。finish 模式需要一个参数即 Libtool 库的安装路径。完成模式使用示例 :$ libtool --modefinish /usr/local/lib卸载模式卸载模式用于卸载已经安装的 Libtool 库或者执行程序。卸载模式使用示例 :$ libtool --modeuninstall rm /usr/local/lib/library.la执行模式执行模式用来执行应用程序它在启动应用程序之前自动的设置好库的路径。执行模式使用示例 :$ libtool --modeexecute gdb program清除模式清除模式和卸载模式差不多只是它用来清除开发过程中的中间文件。清除模式使用示例 :$ libtool --modeclean rm library.la库版本信息和应用程序一样库也需要不断的升级考虑一个第三方应用程序使用了我们之前发布的 libcompress。现在我们对 libcompress 的性能进行了优化并且提供了新的接口所以我们又发布了这个新版本。这引入了几个问题如果以前的应用程序采用静态方式链接那么如果它想使用新库的功能就必须用新库重新链接应用程序。如果是采用动态链接方式那么新库安装后应用程序应该使用新库还是旧库呢并且如何避免新库和旧库之间的冲突呢库版本号可以解决上述这些问题一个动态链接库有一个版本号它在链接时硬编码到动态链接库中当一个应用程序链接动态链接库时它也存储了链接库的版本信息动态加载器 ( 如 ld-linux.so.2) 可以在程序启动时正确的加载版本匹配的库。用 ldd命令可以查看应用程序使用的动态链接库以及它们的版本信息 :$ ldd .libs/lt-mainlinux-gate.so.1 (0x00182000)libcompress.so.0 /home/…/.libs/libcompress.so.0 (0x00c25000)libz.so.1 /lib/libz.so.1 (0x00565000)libc.so.6 /lib/libc.so.6 (0x003ad000)/lib/ld-linux.so.2 (0x0038d000)我们需要用 .libs/lt-main 作为输入文件当前目录下的 main 只是一个封装脚本。从上面的输出可以看到main 程序依赖 libcompress.so 的版本 0。库接口库接口是 应用程序可以和库交流的入口常用的库接口包括 :全局变量、结构体、常数 ;全局函数包括参数类型、数量和返回类型 ;socket、管道以及其它进程间通讯的协议格式 ;当然还有其它的接口。在设计和实现库时应该考虑尽量在将来减少库接口的改变。库版本号通常一个库有两个版本号一个主版本号一个次版本号主版本号指示接口的改变次版本号指示性能增强或者错误修复。但不是每个系统都是如此。在前面的例子中我们可以看到为动态链接库 libcompress.so.0.0.0 建立了两个符号链接 :libcompress.so - libcompress.so.0.0.0libcompress.so.0 - libcompress.so.0.0.0其中 libcompress.so 是供链接器 ( 例如 ld) 使用的它应该指向当前系统中 libcompress 的最新版本这样新程序总是可以链接最新的库版本。libcompress.so.0 是供动态加载器 ( 例如 ld-linux.so.2) 使用的它应该指向当前系统中相同接口号 ( 这里是 0) 的最新版本这样动态链接器就可以加载相同接口最新的库版本。Libtool 库版本号每个系统的库版本机制并不一样Libtool 通过一种抽象的版本机制最终在创建库时映射到具体的系统版本机制。Libtool 的版本号分为 3 个部分 :current: 表示当前库输出的接口的数量 ;revision: 表示当前库输出接口的修改次数 ;age: 表示当前库支持先前的库接口的数量例如 age为 2表示它可以和支持当前库接口的执行文件或者支持前面两个库接口的执行文件进行链接。所以 age应该总是小于或者等于 current。Libtool 的库版本通过参数 -version-infocurrent:revision:age指定例如下面的例子 :$ libtool --modelink gcc -l libcompress.la -version-info 0:1:0如果没有指定默认版本是 0.0.0。注意应该尽可能少的更新库版本号尤其是不能强行将库版本号和软件发行号保持一致下面是更新库版本号的几个策略 :如果修改了库的源代码那么应该增加 revision。这是当前接口的新的修订版本。如果改变了接口应该增加 current将 revision重置为 0。这是接口的新版本。如果新接口是前面接口的超集 ( 前面的接口还是可用 )那么应该增加 age。这是一个向后兼容的版本。如果新接口删除了前面接口的元素那么应该将 age重置为 0。这是一个新的但是不向后兼容的版本。避免版本信息有些动态链接库例如可动态加载模块不需要版本号这时可使用 Libtool 的 -avoid-version选项例如下面的命令 :$ libtool --modelink gcc -o libcompress.la compress.lo -rpath /tmp -avoid-version将只会创建一个 .so 结尾的动态链接库而没有 .0.0.0 这样的版本后缀。结合 Autoconf 和 Automake 使用 Libtool在使用 Autoconf 和 Automake 的项目中使用 Libtool 更加容易只需要添加或者修改几个地方后续由 Automake 来帮你正确的调用 Libtool。和 Autoconf 和 Automake 一样当在其它主机上编译发布的软件包时不需要安装 Libtool。我们以前面的 compress.c 文件为例介绍如何将它转换成一个 Autoconf/Automake/Libtool 项目。建立 configure.ac使用下面的命令建立一个 configure.ac 模板 :$ autoscan这将生成一个 configure.scan 文件将它改名为 configure.ac。在 AC_INIT() 之后加入下面几行 :初始话 AutomakeAM_INIT_AUTOMAKE([-Wall])这是在 Autoconf 中使用 Libtool 唯一必须的宏AC_PROG_LIBTOOL在 AC_OUTPUT 之前加入几行 :告诉 Autoconf 通过 Makefile.in 自动生成 MakefileAC_CONFIG_FILES([Makefile])建立 Makefile.am建立一个 Makefile.am 文件内容如下 :_LTLIBRARIES 是 Automake 支持 Libtool 的原语lib_LTLIBRARIES libcompress.lalibcompress_la_SOURCES compress.c可以通过 _LDFLAGS 传递选项给 Libtoollibcompress_la_LDFLAGS 通过 _LIBADD 可以指定库依赖关系libcompress_la_LIBADD -lz注意上面用 lib_LTLIBRARIES而不是 lib_LIBRARIES这告诉 Automake 使用 Libtool 创建 Libtool 库。建立 configure 和 Makefile用下面的命令建立几个空文件 :$ touch NEWS README AUTHORS ChangeLog然后运行 :$ autoreconf -i -s这将建立 configure 脚本运行它将得到 Makefile:$ ./configure同时configure 也建立了 libtool 脚本后续 Automake 将使用这个 libtool 脚本而不是系统的那个。建立 Libtool 库现在已经有了 Makefile我们只需要简单的输入 :$ make便可以创建 libcompress 了这比手动调用 Libtool 要方便很多。注意 Automake 自动为 Libtool 选择了 -rpath 的路径这是跟随 UNIX 系统习惯定义的库文件安装到 $prefix/lib 目录中头文件安装到 $prefix/include 目录中。我们可以通过 configure 脚本的 --prefix选项改变上面的 $prefix也可以使用 configure 脚本的 --libdir明确的指定库文件的安装目录。静态库和动态库前面在 configure.ac 中的 AC_PROG_LIBTOOL 宏为 configure 脚本添加了两个选项 :–enable-static–enable-shared这两个选项可以控制是否建立动态或者静态链接库例如如果只想建立动态链接库可以这样运行 configure:$ ./configure --enable-shared – disable-static在开发过程中禁止创建动态链接库有几个优势 :编译速度提高了这可以节省时间 ;调试更加容易因为不用处理任何动态链接库引入的复杂性 ;你可以了解 Libtool 在只支持静态链接库的平台的行为 ;为了避免在 configure 时忘记 --disable-shared选项你可以在 configure.ac 中 AC_PROG_LIBTOOL 之前加入一行 :AC_DISABLE_SHARED可动态加载模块Libtool 的 链接模式支持 -module选项它用来建立一个可动态加载模块可以通过 Automake 将这个选项传递给 Libtool。只需要选项添加到 Makefile.am 中的 libcompress_la_LDFLAGS变量即可所以要建立可动态加载模块我们需要修改 Makefile.am:libcompress_la_LDFLAGS -module -avoid-version修改 Makefile.am 之后需要运行 Automake:$ automake这将重新生成 Makefile.in 文件以至于 Makefile。安装 Libtool 库安装 Libtool 库非常的简单只需要运行 :$ make install卸载 Libtool 库和安装 Libtool 库同样简单 :$ make uninstall建立执行程序通过 Automake 使用 Libtool 库也非常容易我们需要在 Makefile.am 中加入下面的几行 :bin_PROGRAMS mainmain_SOURCES main.cmain_LDFLAGS main_LDADD libcompress.la注意在建立 libcompress.la 是我们通过 _LIBADD 指定依赖库而建立执行文件 main 时我们通过 _LDADD 指定依赖库要记住这点区别。也记得把前面为测试可动态加载模块时修改的 libcompress_la_LDFLAGS 变量改回来 :libcompress_la_LDFLAGS 修改 Makefile.am 之后需要运行 Automake 更新 Makefile.in:$ automake然后运行$ make就可以建立可执行程序 main。调试执行程序在结合 Autoconf 和 Automake 使用 Libtool 时我们几乎永远都不会直接调用 Libtool除了一个例外那就是 Libtool 的执行模式。例如我们在开发时要调试执行程序可以使用下面的命令 :$ libtool --modeexecute gdb main如果直接使用 :$ gdb maingdb 会抱怨 main 的格式不可接受因为使用 Libtool 建立的 main 只是一个封装脚本它最终启动的是 .lib/lt-main。小结本文档描述了 GNU Libtool 解决的问题它的工作方式以及在实际工作中使用 Libtool 的方法。有兴趣的读者应该进一步参考 GNU Libtool 手册获得更详细的信息。在安装了 GNU Libtool 的系统中可以直接通过 info libtool 来查看手册。