英文原文:http://www.cprogramming.com/tutorial/shared-libraries-linux-gcc.html
库是程序员必须的工具,它是已经编译过的代码。库通常提供通用的功能,例如链表和二叉树等数据结构,或者特定的功能,例如MySQL等数据库服务器访问接口。
多数大型软件项目有多个部件组成,这些部件有可能在其它项目中用到,或者你只是想分离多个部件以便组织。当你有一个可重用或者逻辑上独立的函数集合,建立一个库使你不用总是复制代码到现在的项目然后重新编译,而且可以保持不同的模块的分离,修改其中一个不会影响其他模块。只要一个库被写好且测试过,就可以安全地重用。
建立静态库是相当简单的,这里只讲如何建立共享库(动态库)。
在开始之前,先看看从源代码到可执行程序这个过程中发生什么:
1、C语言预处理:这个阶段,任何以#开头,例如#define和#include的行都会被处理,也就是宏替换和文件包含。
2、编译:将预处理后的源代码转换成汇编代码,然后在转换成机器代码。
3、链接:将objectfiles和库链接生成可执行程序。对于静态库,链接器把库本身放到最终的可执行程序中;而对于共享库,只是把对库的引用放到里面。
4、装入:当我们执行程序的时候,检查程序对共享库的引用,将动态库映射到程序中。
第3和第4步是共享库比较神奇却也让人困惑的地方。
foo.h:
#ifndef foo_h__
#define foo_h__
extern void foo(void);
#endif // foo_h__
foo.c:
#include <stdio.h>
void foo(void)
{
puts("Hello, I'm a shared library");
}
main.c:
#include <stdio.h>
#include "foo.h"
int main(void)
{
puts("This is a shared library test...");
foo();
return 0;
}
foo.h定义了我们的库的接口,一个函数foo(),foo.c是函数实现的地方。
本例子在路径 /home/username/foo
Step 1:编译不依赖于位置的代码
将库的源代码编译成位置无关的代码
$ gcc -c -Wall -Werror -fpic foo.c
Step 2: 由目标文件(object file)生成共享库
将库的名称定为 libfoo.so:
gcc -shared -o libfoo.so foo.o
Step 3: 链接共享库
先编译 main.c然后链接 libfoo,最终生成程序“test.”-lfoo选项是用来寻找libfoo.so的。 GCC 假定所有库以lib开头,以.so或者.a结尾(.so是代表共享库,.a代表归档或者静态链接库)。
$ gcc -Wall -o test main.c -lfoo /usr/bin/ld: cannot find -lfoo collect2: ld returned 1 exit status
告诉GCC去哪里找共享库
用–L选项,在此例中,共享库在当前目录,也就是/home/username/foo中
$ gcc -L/home/username/foo -Wall -o test main.c -lfoo
Step 4: 使共享库在程序运行时能用上。
$ ./test ./test: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory
因为没有将共享库libfoo.so放在标准目录,所以要给装入器(loader)点帮助。有几个选择:可以用环境变量LD_LIBRARY_PATH,或者rpath。先看看LD_LIBRARY_PATH。
Using LD_LIBRARY_PATH
$ echo $LD_LIBRARY_PATH
这个变量中什么也没有,我们将工作目录加到LD_LIBRARY_PATH中。
$ LD_LIBRARY_PATH=/home/username/foo:$LD_LIBRARY_PATH $ ./test ./test: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory
虽然正在LD_LIBRARY_PATH这个目录中,但是没有export它。在Linux,如果没有export那些对环境变量的改变,它们的改变不会被子进程继承,所以装入器(loader)和test程序没有继承这些改变。好在,很容易解决这个问题:
$ export LD_LIBRARY_PATH=/home/username/foo:$LD_LIBRARY_PATH $ ./test This is a shared library test... Hello, I'm a shared library
Good, it worked! LD_LIBRARY_PATH 用来测试共享库很方便,尤其是当我们没有系统管理员权限的时候。然而,exportLD_LIBRARY_PATH意味着有可能导致其他用到LD_LIBRARY_PATH的程序出问题。
Using rpath
现在试试rpath(首先清空LD_LIBRARY_PATH,以确保是rpath找到共享库)。rpath,也就是运行路径(run path),是在可执行程序中嵌入共享库位置的一种方式,而不是依赖于默认的位置或者环境变量。在链接的阶段,用“-Wl,-rpath=/home/username/foo” 选项,-WL给定链接器一些选项,这些选项以逗号分隔。
$ unset LD_LIBRARY_PATH $ gcc -L/home/username/foo -Wl,-rpath=/home/username/foo -Wall -o test main.c -lfoo $ ./test This is a shared library test... Hello, I'm a shared library
rpath很好用,因为每个程序可以设定独立的共享库位置,而不会像LD_LIBRARY_PATH那样影响其他程序。不过,rpath缺乏灵活性,它要求共享库安装到特定的路径,在系统配置时不灵活。
Using ldconfig to modify ld.so
如果想安装我们的库让任何系统登录用户可以使用,需要管理员权限,理由如下:首先,要把库放到标准位置,可能是/usr/lib或/usr/local/lib,普通用户没有写入权限;其次,要修改ld.so配置文件和缓存。以管理员登陆,执行以下命令:
$ cp /home/username/foo/libfoo.so /usr/lib $ chmod 0755 /usr/lib/libfoo.so
现在,库文件在标准路径下,有着可以被任何人读取的权限。要告诉装入器它是可用的,于是更新缓存:
$ ldconfig
上面的命令会创造一个到共享库的链接,更新缓存。检测以下:
$ ldconfig -p | grep foo libfoo.so (libc6) => /usr/lib/libfoo.so
可见我们的库已经安装好了。在用它之前,先清理LD_LIBRARY_PATH,以防万一:
$ unset LD_LIBRARY_PATH
重新链接我们的可执行程序,不需要–L选项,因为我们的库已经放在默认的位置,且不需要rpath选项:
$ gcc -Wall -o test main.c -lfoo
用ldd命令,看看test是否使用了我们的库:
$ ldd test | grep foo libfoo.so => /usr/lib/libfoo.so (0x00a42000)
然后运行:
$ ./test This is a shared library test... Hello, I'm a shared library