1、链接库的基本知识
库是一种软件组件技术,库里面封装了数据和函数。它的使用,可以是程序模块化。在程序中使用,我们可以称之为程序函数库。
程序函数库可分为3种类型:静态函数库(static libraries)、共享函数库(shared libraries)、动态函数库(dynamically loaded libraries):
1、静态函数库,是在程序执行前就加入到目标程序中去了;
2、共享函数库,则是在程序启动的时候加载到程序中,它可以被不同的程序共享
3、动态函数库,并非另外一种库函数格式,它只是使用动态加载方式加载共享函数库。
Windows系统包括静态链接库(.lib文件)和动态链接库(.dll文件)。
Linux通常把库文件存放在/usr/lib或/lib目录下
Linux库文件名由:前缀lib、库名和后缀3部分组成,其*享链接库以.so.X最为后缀, .X是版本号,静态链接库通常以.a作为后缀。
Linux下标准库链接的三种方式(全静态 , 半静态 (libgcc,libstdc++), 全动态。 三种标准库链接方式的选项及区别见下表。
三种标准库链接方式的选项及区别:
标准库链接方式 | 示例连接选项 | 优点 | 缺点 |
---|---|---|---|
全静态 | -static -pthread -lrt -ldl | 不会发生不同Linux 版本下的标准库不兼容问题 | 生成的文件比较大,应用程序功能受限(不能调用动态库等) |
全动态 | -pthread -lrt -ldl | 生成的文件最小 | 不同Linux版本下标准库依赖不兼容问题 |
半静态(libgcc,libstdc++) | -static-libgcc -L. -pthread -lrt -ldl | 灵活度大,针对不同的标准库采取不同的链接策略,从而避免不兼容问题发生 | 难识别哪些库容易发生不兼容问题,会因选择的标准库版本而丧失某些功能 |
上述三种标准库链接方式中,比较特殊的是 半静态链接方式,主要在于其还需要在链接前增加额外的一个步骤:
ln -s ‘g++ -print-file-name=libstdc++.a’,作用是将 libstdc++.a(libstdc++ 的静态库)符号链接到本地工程链接目录
-print-file-name在gcc中的解释如下: Display the full path to library
ldd 简介:该命令用于打印出某个应用程序或者动态库所依赖的动态库 ,使用该命令我们可以观察到Linux标准库三种链接方式的区别。
从实际应用当中发现,最理想的标准库链接方式就是半静态链接,通常会选择将 libgcc 与 libstdc++ 这两个标准库静态链接,从而避免应用程序在不同 Linux 版本间标准库依赖不兼容的问题发生。
size 简介:该命令用于显示出可执行文件的大小
示例链接选项中所涉及命令(引用 GCC 原文):
- -llibrary
- -l library:指定所需要的额外库
- -Ldir:指定库搜索路径
- -static:静态链接所有库
- -static-libgcc:静态链接 gcc 库
- -static-libstdc++:静态链接 c++ 库
2、静态链接库的创建和使用
涉及命令:ar, ar是创建、修改、提取静态库的操作。
ar -t 显示静态库的内容
ar -d 从库中删除成员文件
ar -r 在库中加入成员文件,若存在,则替换
ar -c 创建一个库
ar -s 无论ar命令是否修改了库内容,都强制重新生成库符号表
步骤如下:
1、在一个头文件种声明静态库所导出的函数。
2、在一个源文件种实现静态库所导出的函数。
3、编译源文件,生成可执行代码。
4、将可执行代码所在的目标文件加入到某个静态库中,并将静态库拷贝到系统默认的存放库文件的目录下。
下面通过一个例子来说明:mylib.h种存放的是静态库提供给用户使用的函数的声明,mylib.c实现了mylib.h种声明的函数。
mylib.h
#ifndef _MYLIB_H_
#define _MYLIB_H_
void weclome(void);
void outString(const char *str);
#endif
mylib.cpp
#include "mylib.h"
void welcome(void){
printf("welcome to libmylib\n");
}
void outString(const char *str){
if(str != NULL)
printf("%s\n", str);
}
test.cpp
#include "mylib.h"
#include
int main(void){
printf("create and use library:\n");
welcome();
outString("it's successful\n");
return 0;
}
- 编译mylib.c生成目标文件:gcc -o mylib.o -c mylib.cpp
- 将目标文件加入到静态库中:ar rcs libmylib.a mylib.o
- 将静态库copy到Linux的库目录(/usr/lib或者/lib)下:cp libmylib.a /usr/lib/libmylib.a
- 使用静态库编译,编译时无需带上前缀和后缀:gcc -o test test.cpp -lmylib
- 运行可执行程序test: ./test
合并静态链接库的脚本代码清单:
echo CREATE demo.a > ar.mac
echo SAVE >> ar.mac
echo END >> ar.mac
ar -M < ar.mac
ar -q demo.a CdtLog.o
echo OPEN demo.a > ar.mac
echo ADDLIB xml.a >> ar.mac
echo SAVE >> ar.mac
echo END >> ar.mac
ar -M < ar.mac
rm ar.mac
Linux makefile 中使用 ar 脚本方式进行静态库的创建,可以编写如下代码:
define BUILD_LIBRARY
$(if $(wildcard $@),@$(RM) $@)
$(if $(wildcard ar.mac),@$(RM) ar.mac)
$(if $(filter %.a, $^),
@echo CREATE $@ > ar.mac
@echo SAVE >> ar.mac
@echo END >> ar.mac
@$(AR) -M < ar.mac
)
$(if $(filter %.o,$^),@$(AR) -q $@ $(filter %.o, $^))
$(if $(filter %.a, $^),
@echo OPEN $@ > ar.mac
$(foreach LIB, $(filter %.a, $^),
@echo ADDLIB $(LIB) >> ar.mac
)
@echo SAVE >> ar.mac
@echo END >> ar.mac
@$(AR) -M < ar.mac
@$(RM) ar.mac
)
endef
$(TargetDir)/$(TargetFileName):$(OBJS)
$(BUILD_LIBRARY)
Linux 静态库链接顺序问题及解决方法:
为了解决这种库链接顺序问题,我们需要增加一些链接选项 :
通过将所有需要被链接的静态库放入 -Xlinker “-(” 与 -Xlinker “-)” 之间,可以是 g++ 链接过程中, 自动循环链接所有静态库,从而解决了原本的链接顺序问题。
3、共享函数库的创建和使用
GNU标准建议所有的函数库文件都放在/usr/local/lib目录下,而且建议命令可执行程序都放在/usr/local/bin目录下。
文件系统层次化标准FHS(Filesystem Hierarchy Standard)规定了在一个发行包中大部分的函数库文件应该安装到/usr/lib目录下,但是如果某些
库是在系统启动的时候要加载的,则放到/lib目录下,而那些不是系统本身一部分的库则放到/usr/local/lib下面。
上面两个路径的不同并没有本质的冲突。GNU提出的标准主要对于开发者开发源码的,而FHS的建议则是针对发行版本的路径的。具体的置信息可以看
/etc/ld.so.conf里面的配置信息,通过对它的修改,可以增加自己的目录。
如果你想覆盖某个库中的一些函数,用自己的函数替换它们,同时保留该库中其他的函数的话,你可以在 /etc/ld.so.preload中加入你想要替换的库
(.o结尾的文件),这些preloading的库函数将有优先加载的权ldconfig可以更新/etc/ld.so.cache。/etc/ld.so.cache可以大大提高访问函数库的速度。
HP-UX系统下,就是用SHLIB_PATH这个变量,而在AIX下则使用LIBPATH这个变量。
共享函数库创建的一个标准命令格式:
gcc -shared -Wl,-soname,your_soname -o library_name file_list library_list
例子:
-
1、创建Object文件:
gcc -fPIC -g -c -Wall a.c
gcc -fPIC -g -c -Wall b.c -
2、创建共享函数库
gcc -shared -Wl,-soname,liblusterstuff.so.1 -o liblusterstuff.so.1.0.1 a.o b.o -lc
如果是C++项目,最简单是使用Cmake来完成共享库的创建,步骤如下:
如果创建的是JNI链接库,则需要将 jdk/include/jni.h 和 jdk/include/linux/jni_md.h 复制到 /usr/include 目录下。反正执行make命令的时候将会报错
1、确保gcc-c++编译环境, 安装命令::
yum install gcc-c++2、安装Cmake
wget https://cmake.org/files/v3.5/cmake-3.5.1.tar.gz
tar -xvf cmake-3.5.1.tar.gz
cd cmake-3.5.1
./bootstrap
make
make install3、如果您使用的windows系统,则将您的项目上传到Linux,进入Linux下该项目的文件夹,创建CMakeLists.txt,内容格式如下:
CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
# cpp 文件
SET(test_SRCS
source/test1.cpp
source/test2.cpp
......
)
# 头文件
SET(test_HDRS
include/test1.h
include/test2.h
.....
)
INCLUDE_DIRECTORIES(include)
# test: 是生产的库的名字, 这里可以加上SHARED或者STATIC或者MODULE,分别表示动态库、静态库、模块。不加则默认是静态库
ADD_LIBRARY(test SHARED/STATIC/MODULE ${test_SRCS} ${test_HDRS})
# 生成可执行文件
# ADD_EXECUTABLE(test ${test_SRCS} ${test_HDRS})
-
4、创建动态链接库:
ccmake directory #用于配置编译选项,如VTK_DIR目录,一般这一步不需要配置
cmake directory #用于根据CMakeLists.txt生成Makefile文件
make #用于执行Makefile文件,编译程序,生成可执行文件
共享函数库的使用
一旦你定义了一个共享函数库,你还需要安装它。其实简单的方法就是拷贝你的库文件到指定的标准的目录(例如/usr/lib),然后运行ldconfig。
如果你没有权限去做这件事情, 那么最简单的方法就是运行ldconfig:
ldconfig -n directory_with_shared_libraries
然后设置LD_LIBRARY_PATH这个环境变量,它是一个以逗号分隔的路径的集合:
LD_LIBRARY_PATH=$LD_LIBRARY_PATH,my_program
如果一个新版的函数库要和老版本的二进制的库不兼容,则soname需要改变。对于C语言,有四种情况会出现不兼容问题:
· 一个函数的行文改变了,这样它就可能与最开始的定义不相符合。
· 输出的数据项改变了。
· 某些输出的函数删除了。
· 某些输出函数的接口改变了。**
4、共享函数库的动态加载
共享函数库可以在程序运行过程中的任何时间加载,它们特别适合在函数中加载一些模块和plugin扩展模块的场合,因为它可以在当程序需要某个
plugin模块时才动态的加载。
Linux系统下,DL函数库与其他函数库在格式上没有特殊的区别。通常C语言环境下,需要包含这个头文件。 Linux中使用的函数和Solaris中一样,都是
dlpoen()API。当然不是所有的平台都使用同样的接口,例如HP-UX使用shl_load()机制,而Windows平台用另外的其他的调用接口。
dlopen()
dlopen函数打开一个函数库然后为后面的使用做准备。C语言原形是:
void * dlopen(const char *filename, int flag);
如果文件名filename是以“/”开头,也就是使用绝对路径,那么dlopne就直接使用它,而不去查找某些环境变量或者系统设置的函数库所在的目录了。
否则dlopen()就会按照下面的次序查找函数库文件:
1. 环境变量LD_LIBRARY指明的路径。
2. /etc/ld.so.cache中的函数库列表。
3. /lib目录,然后/usr/lib。不过一些很老的a.out的loader则是采用相反的次序,也就是先查 /usr/lib,然后是/lib。
dlopen()函数中,参数flag的值必须是RTLD_LAZY或者RTLD_NOW,RTLD_LAZY的意思是resolve undefined symbols as code from the dynamic library is executed,而RTLD_NOW的含义是resolve all undefined symbols before dlopen() returns and fail if this cannot be done'
注意函数库的加载顺序。
dlerror()
通过调用dlerror()函数,我们可以获得最后一次调用dlopen(),dlsym(),或者dlclose()的错误信息。
dlsym()
void * dlsym(void *handle, char *symbol);
函数中的参数handle就是由dlopen打开后返回的句柄,symbol是一个以NIL结尾的字符串。
如果dlsym()函数没有找到需要查找的symbol,则返回NULL。典型的调用过程如下:
dlerror(); /*clear error code */
s = (actual_type)dlsym(handle, symbol_being_searched_for);
if((error = dlerror()) != NULL){
/* handle error, the symbol wasn't found */
} else {
/* symbol found, its value is in s */
}
dlclose()
dlopen()函数的反过程就是dlclose()数,dlclose()函数用力关闭一个DL函数库。真正释放的时候,如果函数库里面有_fini()这个函数,则自动调用_fini()这个函数,做一些必要的处理。Dlclose()返回0表示成功,其他非0值表示错误。
动态函数库的创建:
动态函数库并非另外一种库函数格式,可只是在程序运行的任何时候动态的加载的共享函数库或。它的创建可以参考共享函数库的创建。
动态函数库的使用:
int main(int argc, char *argv){
void *handle;
char *error;
double (*cosine )(double);
handle = dlopen("/lib/libm.so.6", RTLD_LAZY);
if(!handle){
fputs(dlerror(), stderr);
exit(1);
}
cosine = dlsym(handle, "cos");
if((error = dlerror()) != NULL){
fputs(error, stderr);
exit(1);
}
printf("%f", (*cosine)(2, 0));
dlclose(handle);
return 0;
}
如果这个程序名字叫test.c,那么用下面的命令来编译:
gcc -o test test.c –ldl