2009年4月1日星期三

利用libtool自动生成动态库

libtool是什么?
使用libtool的好处在于该工具替user处理一些细节,最主要的是:
1。自动处理库的依赖关系;2。对于共享库,自动加入运行时搜索路径(在GNU/LINUX下面通过在
executable中hardcode一个路径名实现)。3,其他好处(我还不知道)。
而用户在链接生成executable的时候只需要使用libtool工具,并指定libtool库文件的名字作为参数就可以。

=============================================

生成库:
$ libtool --mode=compile gcc -c hello.c
$ libtool --mode=link gcc -g -O -o libhello.la foo.lo hello.lo
-rpath /usr/local/lib -lm
注意:
手册上说libtool库类型有三种:
1。libtool shared library,
必须加入-rpath选项(不能加入-static),同时也产生libtool static library.
2。libtool static library(好像又叫libtool static convenience
library),必须加入-rpath,同时加入-static选项。
3。convenience library
两个参数都不加,它与2。的区别在于convenience library具有PIC特性。


=======================================
安装库:(摘于手册)
在gnulinux下:libtool的安装过程如下:
a23# libtool --mode=install cp libhello.la
/usr/local/lib/libhello.la
cp libhello.la /usr/local/lib/libhello.la
cp .libs/libhello.a /usr/local/lib/libhello.a
ranlib /usr/local/lib/libhello.a
a23#
或者
burger# libtool --mode=install install -c libhello.la
/usr/local/lib/libhello.la
install -c .libs/libhello.so.0.0 /usr/local/lib/libhello.so.0.0
install -c libhello.la /usr/local/lib/libhello.la
install -c .libs/libhello.a /usr/local/lib/libhello.a
ranlib /usr/local/lib/libhello.a
burger#
注意:用户可以先安装再使用(即用executables
libhello.la来链接);好像也可以先链接再安装?!
如果最终使用不安装的libtool库文件,有时候会有问题的!

==========================================

使用库:

libtool --mode=link gcc -o hello main.c /home/szx/test/libadd.la

注意:如果在libtool链接命令中使用-L/-l参数,那么库名字首先被解释为la库文件(在当前路径下和-L指定的目录下搜索),
如果找不到再解释为.a或.so文件(此时libtool功能就退化成gcc或ld了)!所以为了避免二义性,最好使用绝对路径的形式,比如/home/szx/test/libadd.la


1 libtool的工作原理
libtool
是一个通用库支持脚本,将使用动态库的复杂性隐藏在统一、可移植的接口中;使用libtool的标准方法,可以在不同平台上创建并调用动态库。可以认为libtool是gcc的一个抽象,其包装了gcc(或者其他的编译器),用户无需知道细节,只要告诉libtool需要编译哪些库即可,libtool将处理库的依赖等细节。libtool只与后缀名为lo、la为的libtool文件打交道。
libtool主要的一个作用是在编译大型软件的过程中解决了库的依赖问题;将繁重的库依赖关系的维护工作承担下来,从而释放了程序员的人力资源。libtool提供统一的接口,隐藏了不同平台间库的名称的差异等细节,生成一个抽象的后缀名为la高层库libxx.la(其实是个文本文件),并将该库对其它库的依赖关系,都写在该la的文件中。该文件中的dependency_libs记录该库依赖的所有库(其中有些是以.la文件的形式加入的);libdir则指出了库的安装位置;library_names记录了共享库的名字;old_library记录了静态库的名字。
当编译过程到link阶段的时候,如果有下面的命令:
$libtool --mode=link gcc -o myprog -rpath /usr/lib –L/usr/lib –la
libtool会到/usr/lib路径下去寻找liba.la,然后从中读取实际的共享库的名字(library_names中记录了该名字,比如liba.so)和路径(lib_dir中记录了,比如libdir='/usr/lib'),返回诸如/usr/lib/liba.so的参数给激发出的gcc命令行。
如果liba.so依赖于库/usr/lib/libb.so,则在liba.la中将会有dependency_libs='-L/usr/lib
-lb'或者dependency_libs='/usr/lib/libb.la'的行,如果是前者,其将直接把"-L/usr/lib
–lb"当作参数传给gcc命令行;如果是后者,libtool将从/usr/lib/libb.la中读取实际的libb.so的库名称和路径,然后组合成参数"/usr/lib/libb.so"传递给gcc命令行。
当要生成的文件是诸如libmylib.la的时候,比如:
$libtool --mode=link gcc -o libmylib.la -rpath /usr/lib –L/usr/lib –la
其依赖的库的搜索基本类似,只是在这个时候会根据相应的规则生成相应的共享库和静态库。
注意:libtool在链接的时候只会涉及到后缀名为la的libtool文件;实际的库文件名称和库安装路径以及依赖关系是从该文件中读取的。
2 为何使用 -Wl,--rpath-link -Wl,DIR?
使用libtool解决编译问题看上去没什么问题:库的名称、路径、依赖都得到了很好的解决。但下结论不要那么着急,一个显而易见的问题就是:并不是所有的库都是用libtool编译的。
比如上面那个例子,
$libtool --mode=link gcc -o myprog -rpath /usr/lib –L/usr/lib –la
如果liba.so不是使用libtool工具生成的,则libtool此时根本找不到liba.la文件(不存在该文件)。这种情况下,libtool只会把"–L/usr/lib
–la"当作参数传递给gcc命令行。
考虑以下情况:要从myprog.o文件编译生成myprog,其依赖于库liba.so(使用libtool生成),liba.so又依赖于libb.so(libb.so的生成不使用libtool),而且由于某种原因,a对b的依赖并没有写入到liba.la中,那么如果用以下命令编译:
$libtool --mode=link gcc -o myprog -rpath /usr/lib –L/usr/lib –la
激发出的gcc命令行类似于下面:
gcc –o myprog /usr/lib/liba.so
由于liba.so 依赖于libb.so(这种依赖可以用readelf读liba.so的ELF文件看到),而上面的命令行中,并没有出现libb.so,于是,可能会出现问题。
说"可能",是因为如果在本地编译的情况下,gcc在命令行中找不到一个库(比如上面的liba.so)依赖的其它库(比如libb.so),链接器会按照某种策略到某些路径下面去寻找需要的共享库:
1. 所有由'-rpath-link'选项指定的搜索路径.
2. 所有由'-rpath'指定的搜索路径.
'-rpath'跟'-rpath_link'的不同之处在于,由'-rpath'指定的路径被包含在可执行文件中,并在运行时使用,
而'-rpath-link'选项仅仅在连接时起作用.
3. 在一个ELF系统中, 如果'-rpath'和'rpath-link'选项没有被使用,
会搜索环境变量'LD_RUN_PATH'的内容.它也只对本地连接器起作用.
4. 在SunOS上, '-rpath'选项不使用, 只搜索所有由'-L'指定的目录.
5. 对于一个本地连接器,环境变量'LD_LIBRARY_PATH'的内容被搜索.
6.
对于一个本地ELF连接器,共享库中的`DT_RUNPATH'和`DT_RPATH'操作符会被需要它的共享库搜索.
如果'DT_RUNPATH'存在了, 那'DT_RPATH'就会被忽略.
7. 缺省目录, 常规的,如'/lib'和'/usr/lib'.
8. 对于ELF系统上的本地连接器, 如果文件'/etc/ld.so.conf'存在,
这个文件中有的目录会被搜索.
从以上可以看出,在使用本地工具链进行本地编译情况下,只要库存在于某个位置,gcc总能通过如上策略找到需要的共享库。但在交叉编译下,上述八种策略,可以使用的仅仅有两个:-rpath-link,-rpath。这两个选项在上述八种策略当中优先级最高,当指定这两个选项时,如果链接需要的共享库找不到,链接器会优先到这两个选项指定的路径下去搜索需要的共享库。通过上面的描述可以看到:-rpath指定的路径将被写到可执行文件中;-rpath-link则不会;我们当然不希望交叉编译情况下使用的路径信息被写进最终的可执行文件,所以我们选择使用选项-rpath-link。
gcc的选项"-Wl,--rpath-link
–Wl,DIR"会把-rpath-link选项及路径信息传递给链接器。回到上面那个例子,如果命令行中没有出现libb.so,但gcc指定了"-Wl,--rpath-link
–Wl,DIR",则链接器找不到libb.so的时候,会首先到后面-rpath-link指定的路径去寻找其依赖的库。此处我们使用的编译命令的示例是使用unicore平台的工具链。
$ unicore32-linux-gcc –o myprog /usr/lib/liba.so \
-Wl,--rpath-link -Wl,/home/UNITY_float/install/usr/lib
这样,编译器会首先到"/home/UNITY_float/install/usr/lib"下面去搜索libb.so
libtool如何把选项"-Wl,--rpath-link
–Wl,DIR"传递给gcc?libtool中有一个变量"hardcode_libdir_flag_spec",该变量本来是传递"-rpath"选项的,但我们可以修改它,添加我们需要的路径,传递给unicore32-linux-gcc。
"hardcode_libdir_flag_spec"原来的定义如下:
hardcode_libdir_flag_spec="\${wl}--rpath \${wl}\$libdir"
我们修改后的定义如下:
hardcode_libdir_flag_spec="\${wl}—rpath-link \${wl}\$libdir \
-Wl,--rpath-link -Wl,/home/UNITY_float/install/usr/lib \
-Wl,--rpath-link
-Wl,/home/UNITY_float/install/usr/X11R6/lib "
这样,当libtool在"--mode=link"的模式下,就会把选项"-Wl,--rpath-link
–Wl,DIR"传递给gcc编译器了。


利用libtool自动生成动态库
#
1. autoscan命令在当前目录生成configure.scan文件, 内容为:
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.57)
AC_INIT(FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS)
AC_CONFIG_SRCDIR([src/bot.h])
AC_CONFIG_HEADER([config.h])
# Checks for programs.
AC_PROG_CXX
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
AC_HEADER_STDC
AC_CHECK_HEADERS([limits.h malloc.h stdlib.h string.h unistd.h])
# Checks for typedefs, structures, and compiler characteristics.
AC_HEADER_STDBOOL
AC_C_CONST
AC_C_INLINE
# Checks for library functions.
AC_FUNC_MALLOC
AC_FUNC_REALLOC
AC_CHECK_FUNCS([memset strcasecmp strchr strdup])
AC_OUTPUT
将其该名为configure.ac 然后修改:
configure.ac 文件是 autoconf 的输入文件,经过 autoconf 处理,展开里面的
m4宏,
输出的是 configure 脚本。
第 4 行声明本文件要求的 autoconf 版本,因为本例使用了新版本
2.57,所以在此注明。
第 5 行 AC_INIT 宏用来定义软件的名称和版本等信息
AC_INIT([test], 1.0,****@gmail.com)
增加版本信息(为生成lib库做准备)
lt_major=1
lt_age=1
lt_revision=12
dist_version=0.1.12
AM_INIT_AUTOMAKE(test, $dist_version) //自动生成Makefile文件
增加宏, 打开共享库
AC_PROG_LIBTOOL
# Check for dl
DL_PRESENT=""
AC_CHECK_LIB( dl, dlopen, DL_PRESENT="yes",, $DL_LIBS -ldl )
if test "x$DL_PRESENT" = "xyes"; then
AC_DEFINE(HAVE_LIBDL, 1, [Define if DL lib is present])
DL_LIBS="-ldl"
AC_SUBST(DL_LIBS)
fi
# Check for libm
M_PRESENT=""
AC_CHECK_LIB( m, sin, M_PRESENT="yes",, $M_LIBS -lm )
if test "x$M_PRESENT" = "xyes"; then
AC_DEFINE(HAVE_LIBM, 1, [Define if libm is present])
M_LIBS="-lm"
AC_SUBST(M_LIBS)
fi
增加依赖库
# Check for pthread
PTHREAD_PRESENT=""
AC_CHECK_LIB( pthread, pthread_create, PTHREAD_PRESENT="yes",,
$PTHREAD_LIBS
-lpthread )
if test "x$PTHREAD_PRESENT" = "xyes"; then
AC_DEFINE(HAVE_LIBPTHREAD, 1, [Define if libpthread is present])
PTHREAD_LIBS="-lpthread"
AC_SUBST(PTHREAD_LIBS)
fi
要生成项目工程目录和其它目录下的Makefile 文件, 必需加入
AM_CONFIG_FILES的宏:
例如: AC_CONFIG_FILES([Makefile
src/Makefile
data/Makefile
docs/Makefile])
修改完后Makefile.ac如下:
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.57)
AC_INIT([test],[1.0],[****@users.sourceforge.net])
AM_CONFIG_HEADER(config.h)
lt_major=1
lt_age=1
lt_revision=12
dist_version=0.1.12
AM_INIT_AUTOMAKE(test, $dist_version)
AC_SUBST(lt_major)
AC_SUBST(lt_revision)
AC_SUBST(lt_age)
# Checks for programs.
#AC_PROG_CC
#AC_PROG_INSTALL
#AC_PROG_LN_S
#AC_PROG_LIBTOOL
AM_PROG_LIBTOOL
# Checks for libraries.
pkg_modules="gtk+-2.0 >= 2.0.0"
PKG_CHECK_MODULES(GTK_PACKAGE, [$pkg_modules], HAVE_GTK2="yes",
HAVE_GTK2="no" )
AC_SUBST(GTK_PACKAGE_CFLAGS)
AC_SUBST(GTK_PACKAGE_LIBS)
# Check for dl
DL_PRESENT=""
AC_CHECK_LIB( dl, dlopen, DL_PRESENT="yes",, $DL_LIBS -ldl )
if test "x$DL_PRESENT" = "xyes"; then
AC_DEFINE(HAVE_LIBDL, 1, [Define if DL lib is present])
DL_LIBS="-ldl"
AC_SUBST(DL_LIBS)
fi
# Check for libm
M_PRESENT=""
AC_CHECK_LIB( m, sin, M_PRESENT="yes",, $M_LIBS -lm )
if test "x$M_PRESENT" = "xyes"; then
AC_DEFINE(HAVE_LIBM, 1, [Define if libm is present])
M_LIBS="-lm"
AC_SUBST(M_LIBS)
fi
# Check for pthread
(如示例中检测pthread,在生成的Makefile就会自动添加-lpthread)
PTHREAD_PRESENT=""
AC_CHECK_LIB( pthread, pthread_create, PTHREAD_PRESENT="yes",,
$PTHREAD_LIBS
-lpthread )
if test "x$PTHREAD_PRESENT" = "xyes"; then
AC_DEFINE(HAVE_LIBPTHREAD, 1, [Define if libpthread is present])
PTHREAD_LIBS="-lpthread"
AC_SUBST(PTHREAD_LIBS)
fi
# Checks for header files.
#AC_HEADER_DIRENT
#AC_HEADER_STDC
#AC_CHECK_HEADERS([fcntl.h stdlib.h string.h sys/time.h unistd.h])
# Checks for typedefs, structures, and compiler characteristics.
#AC_TYPE_PID_T
#AC_TYPE_SIZE_T
#AC_HEADER_TIME
# Checks for library functions.
#AC_FUNC_CLOSEDIR_VOID
#AC_FUNC_MALLOC
#AC_CHECK_FUNCS([memset strstr])
AC_CONFIG_FILES([Makefile
src/Makefile
data/Makefile
doc/Makefile])
AC_OUTPUT

2.生成各目录下的Makefile.am文件
./Makefile.am //工程目录下
SUBDIR = src data doc
../src/Makefile.am //源码目录下
MAINTAINERCLEANFILES = Makefile.in
INCLUDES = -I../include
CPPFLAGS=-DINSTALL_PREFIX="\"$(prefix)\""
lib_LTLIBRARIES = libtest.la
libtest_la_LDFLAGS = -version-info @lt_major@:@lt_revision@:@lt_age@
libtest_la_SOURCES = \
test.c \
test_private.h \
check_match.c \
check_match.h \
test_helpers.c \
test_helpers.h \
debug.h
libtest_la_LIBADD = \
@DL_LIBS@ \
@M_LIBS@

3. 生成autogen.sh脚本, 内容
#! /bin/sh
set -x
aclocal
autoheader
automake --foreign --add-missing --copy
autoconf

保存后修改权限 chmod a+x autogen.sh

3.运行脚本./autogen.sh, 生成configure脚本. 这里可能会遇到错误,
可以根据错误提示作相应修改.

4.运行./configure脚本.自动生成src目录下的makefile文件

5. 切换到目录src, 运行make 自动在当前目录下建立.libs文件,
编程生成的库文件就保存在该目录下.
make install 安装在默认目录 /usr/local/lib/下.

6.如果要生成其它的安装目录,Makefile.am就要这样写
MAINTAINERCLEANFILES = Makefile.in
INCLUDES = -I../include
lib_LTLIBRARIES = libtt.la
libdir = $(prefix)/lib/test
libtt_la_LDFLAGS = -version-info @lt_major@:@lt_revision@:@lt_age@
libtt_la_LIBADD = @PTHREAD_LIBS@
libtt_la_SOURCES = \
tt.c \
video.c \
video.h

没有评论: