Linux下动态链接库的创建和使用

 转自:Linux下动态链接库的创建和使用

http://blog.csdn.net/xlxxcc/article/details/51074150


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;
}
  1. 编译mylib.c生成目标文件:gcc -o mylib.o -c mylib.cpp
  2. 将目标文件加入到静态库中:ar rcs libmylib.a mylib.o
  3. 将静态库copy到Linux的库目录(/usr/lib或者/lib)下:cp libmylib.a /usr/lib/libmylib.a
  4. 使用静态库编译,编译时无需带上前缀和后缀:gcc -o test test.cpp -lmylib
  5. 运行可执行程序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) 
  • 1

Linux 静态库链接顺序问题及解决方法: 
为了解决这种库链接顺序问题,我们需要增加一些链接选项 : 
(CXX)(LINKFLAGS) (OBJS)Xlinker"("(LIBS) -Xlinker “-)” -o $@ 
通过将所有需要被链接的静态库放入 -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 install

  • 3、如果您使用的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


猜你喜欢

转载自blog.csdn.net/qqwangfan/article/details/79103514
今日推荐