欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-06-03 21:46:25
...

 转自: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 [email protected]),@$(RM) [email protected]) 
 $(if $(wildcard ar.mac),@$(RM) ar.mac) 
 $(if $(filter %.a, $^), 
 @echo CREATE [email protected] > ar.mac 
 @echo SAVE >> ar.mac 
 @echo END >> ar.mac 
 @$(AR) -M < ar.mac 
 ) 
 $(if $(filter %.o,$^),@$(AR) -q [email protected] $(filter %.o, $^)) 
 $(if $(filter %.a, $^), 
 @echo OPEN [email protected] > 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 [email protected] 
通过将所有需要被链接的静态库放入 -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