静态链接库
前言
静态库是obj文件的一个集合(目标文件中通常仅解析了文件内部的变量和函数,对于引用的函数和变量还没有解析,这需要将其他已经编写好的目标文件引用进来,将没有解析的函数和变量进行解析,通常引用的目标是库),通常静态库以".a"为后缀,名字格式一般为libxxx.a。静态库由程序ar生成。
实例程序如下:
Main.c
#include <stdio.h>
extern void print_hello();
int
main(void)
{
print_hello();
}
Print_hello.c
#include <stdio.h>
void
print_hello()
{
printf("hello ");
}
-
生成静态链接库
创建静态库的步骤:
- 生成目标文件。(使用命令gcc –c file.c)
- 使用工具ar对目标文件进行归档。(使用的命令如下)
生成静态链接库,或者将一个obj文件加入到已经存在的静态库的命令格式为:
ar –rcs 库文件obj_1 obj_2 …
使用上面的实例程序print_hello.c创建静态链接库:
-
使用静态链接库
使用方式一:
使用方式二:
注意,在方法二中"-L./"不可少,否则出现如下错误:
这是因为上面的命令在系统默认的路径下查找hello函数库,而我们并没有将libhello.a库放在系统默认搜索路径下,所以需要显示指定库函数的路径为当前目录。
另外还需注意,在使用-l选项时,-o选项的目标名称要在-l链接的库名称之前,否则gcc会认为-l是生成的目标而出错。
下面两个图分别是头文件和库文件的默认搜索路径:
动态链接库
前言
动态链接库是程序运行时加载的库,当动态链接库正确安装后,所有的 程序都可以使用动态库来运行程序。动态链接库是目标文件的集合,目标文件在动态链接库中的组织方式是按照特殊方式形成的。库中函数和变量的地址是相对地址,不是绝对地址,其真实地址在调用动态库的程序加载时形成。
动态链接库的名称有别名(soname)、真名(realname)和链接名(linker name):
别名:libxxx.so,这种形式的库名正是执行编译命令时编译器要搜索的名字。
真名:动态链接库的真实名称,一般总是在别名的基础上加上一个小版本号、发布版本等构成。
链接名:程序链接时使用的库的名字。
-
生成动态链接库
生成动态链接库的命令很简单,使用-fPIC选项或者-fpic选项。-fPIC和-fpic选项的作用是使得gcc生成的代码是位置无关的,例如下面的命令将print_hello.c编译生成动态链接库:
其中-shared选项告诉编译器生成一个动态链接库;-soname,libhello.so表示生成动态库的别名是libhello.so;-o libhello.so.1选项择表示生成名字为libhello.so.1的实际动态链接库文件。
生成动态链接库之后一个很重要的问题就是安装,一般情况下将生成的动态链接库复制到系统默认的动态链接库的搜索路径下,通常有/lib,/usr/lib,/usr/local/lib,放到其中任何一个目录下都可以。
-
动态链接库的配置
动态链接库并不是可以随意地使用,要在运行的 程序中使用动态链接库,需要指定系统的动态链接库搜索的路径,让系统找到运行所需要的动态链接库才可以。
系统中的配置文件/etc/ld.so.conf是动态链接库的搜索路径配置文件。这这个文件内,存放着可被Linux共享的动态链接库所在目录的名字(系统目录/lib,/usr/lib除外,这两个目录默认就是动态链接库的搜索路径),多个目录名间以空白字符(空格、换行等)或冒号或逗号分割。查看系统中的动态链接库配置文件的内容:
Linux的配置文件将目录ld.so.conf.d/中的配置文件包含了进来。
-
动态链接库管理命令
为了让新增加的动态链接库能够被系统共享,需要运行动态链接库的 管理命令ldconfig。ldconfig命令的作用是在系统的默认搜索路径和动态链接库配置文件中所列出的目录里搜索动态链接库,创建动态链接库装入程序需要的链接和缓存文件。搜索完毕后,将结果写入缓存文件/etc/ld.so.cache中,文件中保存的是已经排好序的动态链接库名字列表。
ldconfig命令的用法如下:
-
使用动态链接库
把当前工作目录(libhello.so.1所在的目录)加入动态链接库的搜索路径配置文件/etc/ld.so.conf中:
执行ldconfig命令刷新缓存文件/etc/ld.so.cache:
我们会发现,执行ldconfig命令后,当前目录下生成了一个新的文件libhello.so(这是-soname,libhello.so选项导致的,如果没有该选项,那么ldconfig执行后不会生成新文件libhello.so。),使用ls命令会发现此文件是libhello.so.1的链接文件:
在编译程序时,使用动态链接库和静态链接库是一致的,使用"-lxxx"的方式:
编译后执行时出现如下错误:
其主要原因是使用了SELINUX。将其状态设置为disabled即可,方法如下:
打开/etc/selinux/config,将selinux=enforcing或permissive改成disabled。然后存盘退出,重启系统。
再次执行,结果如下:
运行时,还可能出现这样的错误:test: error while loading shared libraries: libxxx.so: cannot open shared object file: No such file or directory,这是由于程序运行时没有找到动态链接库造成的。程序编译时链接动态链接库和运行时使用动态链接库的概念是不同的,在运行时,程序链接的动态链接库需要在系统目录下才行。有几种办法可以解决此种问题:
- 将动态链接库的目录放到程序搜索路径中,可以将库的路径添加到环境变量LD_LIBRARY_PATH中实现:
- 使用ld-Linux.so.2来加载程序,命令格式为:
/lib/ ld-Linux.so.2 –library-path 路径 程序名
加载test程序的命令为:
不过貌似并不是所有系统上都有ld-Linux.so.2。
注意,如果系统的搜索路径下同时存在静态链接库和动态链接库,默认情况下会链接动态链接库。如果需要强制链接静态链接库,需要加上"-satic"选项。
动态加载库
前言
动态加载库和一般的动态链接库所不同的是,一般动态链接库在程序启动时就要寻找动态库,找到库函数;而动态加载库可以用程序的方法来控制什么时候加载。动态加载库主要有函数dlopen()、dlerror()、dlsym()和dlclose()来控制动态库的使用。函数原型如下:
-
打开动态库dlopen()
函数dlopen()按照用户指定的方式打开动态链接库,其中参数filename为动态链接库的文件名,flag为打开方式,一般为RTLD_LASY,函数的返回值为库的指针。
例如,下面的代码使用dlopen打开当前目录下的动态库libhello.so:
void *phandle = dlopen("./libhello.so", RTLD_LAZY);
-
获得函数指针dlsym()
使用动态链接库的目的是调用其中的函数,完成特定的功能。函数dlsym()可以获得动态链接库中指定函数的指针,然后可以使用这个函数指针进行操作。其中参数handle为dlopen()打开动态库后返回的句柄,参数symbol为函数的名称,返回值为函数指针。
-
使用动态加载库实例
#include <stdio.h>
#include <dlfcn.h>
int
main(void)
{
void (*printhello)(void);
void *phandle = NULL;
char *perr = NULL;
phandle = dlopen("./libhello.so", RTLD_LAZY);
if(!phandle)
{
printf("Failed load library! ");
}
perr = dlerror();
if(perr != NULL)
{
printf("%s ", perr);
return(0);
}
printhello = dlsym(phandle, "print_hello");
perr = dlerror();
if(perr != NULL)
{
printf("%s ", perr);
return(0);
}
(*printhello)();
dlclose(phandle);
return(0);
}
编译运行:
注意,想要使用dlfcn.h中的函数,编译时必须加上选项-ldl。
小发现:
有没有注意到,我们并没有使用用到include <hello.h>(假设我们为print_hello.c写了一个hello.h的头文件)。其实,只要编译命令中加入选项-lhello,hello.h头文件包含不包含都没有问题。为了验证这个问题,使用http://www.cnblogs.com/nufangrensheng/p/3518411.html中的程序清单11-1这个实例进行了测试:
在http://www.cnblogs.com/nufangrensheng/p/3518411.html程序清单11-1编译过程中,曾遇到undefined reference to ‘pthread_create’这样的错误,原因在于没有在编译命令中加入-lpthread选项。
如果我们加入了-lpthread选项,此时尝试着把程序中的#include <pthread.h>去掉,重新编译你会发现同样也是可以的。
总结:如果使用的是标准库中的函数,则只需将头文件包含进来。如果使用的函数所在的库不是标准库(例如我们自己编写的库或标准以外扩展的库),则在编译时必须加入-lxxx选项,而头文件则可以包含也可以不包含,包含进来显得规范而已。