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

来源:互联网 发布:淘宝80字好评 编辑:程序博客网 时间:2024/06/08 11:13

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) 

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

0 0
原创粉丝点击