当前位置: 首页 > 科技观察

Linux库的概念,动态库和静态库的制作,如何移植第三方库

时间:2023-03-21 21:52:55 科技观察

1.什么是库?windows平台下和linux平台下都有大量的库。一般是软件作者为便于分发、替换或二次开发而发布的一组二进制可重定位目标代码文件,可在编译时或运行时与应用程序链接。本质上,库是可执行代码的二进制形式。这个文件可以在编译时直接被编译器链接成可执行程序,也可以在运行时根据需要由操作系统的运行环境动态加载到内存中。一组库形成一个发布包。当然,发布多少个库完全取决于库提供者。由于windows和linux的性质不同,这两个库的二进制文件是不兼容的。实际上,每个程序都依赖于许多基础底层库。不可能每个人都从头开始,所以图书馆的存在是很有意义的。共享库的好处是如果不同的应用程序调用同一个库,内存中只有一个共享库的实例。本文只讨论linux下的库。2、库的分类库有两种类型:静态库和共享库(动态库)。在win32平台上,静态库通常后缀为.lib,动态库后缀为.dll;在linux平台上,静态库的后缀通常是.a,动态库的后缀是.so。本质上,同一个程序编译出的静态库和动态库在功能上没有区别。区别仅在于它们的名称,即“静态”和“动态”。两者都以文件的形式存在,本质上是一种二进制格式的可执行代码,可以加载到内存中执行。无论是动态链接库还是静态链接库,它们无非就是为调用者提供变量、函数和类。1.静态库所谓静态库就是编译器在静态编译时搜索并链接到指定的目录。一旦链接完成,最终的可执行程序就包含了库文件中所有有用的信息,包括代码段、数据段等。2.动态库所谓动态库是指应用程序运行时,操作系统根据应用程序的请求,动态查找并加载到指定目录下的内存中,同时需要进行地址重定向。3.差异我们讨论静态库和动态库在编译、链接和加载时间方面的差异。编译链接静态链接库会在程序编译时链接到目标代码中,目标程序运行时不再需要修改动态库。库被链接到一个可执行文件中,这导致可执行文件的大小更大。动态库在程序编译时并没有链接到目标代码中,而是在程序运行时加载,因为可执行文件体积小。有了动态库,程序升级就会相对简单。比如升级动态库,只需要替换动态库的文件,不需要替换可执行文件。但需要注意的是,可执行程序需要在运行时能够找到动态库文件。可执行文件是动态库的调用者。程序代码和库加载时间的区别在于代码加载的时间。静态库的代码在编译过程中已经加载到可执行程序中,所以体积比较大。共享库的代码在可执行程序运行时加载到内存中,在编译过程中只是简单地引用,因此代码量小。4.优缺点静态库与动态库相比,优点是直接链接到可执行程序中。之后,可执行程序不再依赖于运行环境的设置(当然仍然依赖于CPU指令集和运行系统支持的可执行文件格式等硬限制)。动态库的优点是用户甚至可以在程序运行时随时更换动态库,这构成了动态插件系统的基础。静态库和动态库的具体使用由程序员根据需要决定。三、库文件的制作1、库文件的名称静态库的名称一般为libxxxx.a,其中xxxx为lib的名称;动态库的名称一般为libxxxx.so.x.y.z,含义如下图所示:2.库文件的生产常用参数首先要了解gcc编译库时带有一些参数。参数含义-shared指定生成动态链接库。-static指定生成静态链接库。-fPIC表示编译为与位置无关的代码,用于编译共享库。目标文件需要创建为与位置无关的代码,这在概念上意味着它们可以在可执行文件加载它们时放置在可执行文件内存中的任何位置。-L表示要链接的库在当前目录。-l指定链接所需的动态库。编译器在查找动态链接库时有一个隐含的命名规则,即在给定名称前加lib,在其后加.so来确定库名。-Wall生成所有警告消息。-ggdb此选项将生成尽可能多的调试信息供gdb使用。-g编译器在编译时产生调试信息。-c只激活预处理、编译和汇编,即使程序成为目标文件(.o文件)。-Wl,options将参数(选项)传递给链接器ld。如果选项中间有一个逗号,则将选项分成多个选项,然后传递给链接器。3.库源文件假设我们要将以下两个文件做成库文件add.cintadd(intx,inty){returnx+y;}intsub(intx,inty){returnx-y;}add.hintadd(intx,inty);intsub(intx,inty);4。制作静态库并使用1.需要将add.c编译成.o文件gcc-cadd.c2。使用ar命令生成静态库libadd.aar-rclibadd.aadd.o遵循静态库命名规则lib+name+.a3。使用静态库如果使用静态库libadd.a,只需要包含add.h,就可以使用函数add()和sub()。#include#include"add.h"voidmain(){printf("add(5,4)is%d\n",add(5,4));printf("sub(5,4))is%d\n",sub(5,4));}静态库文件可以放在任意位置,编译时只需要找到库文件即可。gcctest.c-orunlibadd.a4。如果库和头文件编译在其他目录下使用以下命令:gcc-c-I/home/xxxx/includetest.c//假设test.c使用对应的静态库gcc-otest-L/home/xxxxx/libtest.olibadd.a或gcc-c-I/home/xxxx/include-L/home/xxxxx/liblibadd.atest.c1)。通过-I(是大i)指定对应的头文件2)。库文件的路径由-L指定,libadd.a为要使用的静态库。3).静态库的头文件应该包含在test.c中。五、制作动态库并使用1.将add.c编译成动态链接库libadd.sogcc-fPIC-olibadd.o-cadd.cgcc-shared-olibadd.solibadd.o也可以直接使用一条命令gcc-fPIC-共享-olibadd.soadd.c2。动态库的安装一般复制到/lib:sudocplibadd.so/lib3。使用动态库#include#include"add.h"voidmain(){printf("add(5,4)is%d\n",add(5,4));printf("sub(5,4)is%d\n",sub(5,4));}编译动态库:gccstatic-orun-ladd编译时注意观察动态库名称与库文件的对应关系.libadd.so<-------->-ladd去掉.so,lib简化为l,其他字母保留。6.动态加载函数库DynamicallyLoaded(DL)Libraries动态加载函数库动态加载(DL)库是函数库的一种,可以在程序运行过程中随时加载。它们特别适合在函数中加载一些模块和插件扩展模块,因为它可以在程序需要某个插件模块时动态加载。在Linux系统下,DL函数库与其他函数库在格式上没有特别的区别,都是以标准的对象格式创建的。主要区别在于,这些函数库不是在程序链接或启动时加载的,而是通过API打开函数库、查找符号表、处理错误和关闭函数库。通常在C语言环境下,需要包含这个头文件。dlopen()dlopen函数打开一个函数库并准备它供以后使用。C语言原型为:void*dlopen(constchar*filename,intflag);parameterfilename如果文件名filename以“/”开头,即使用绝对路径,那么dlopne会直接使用它,而不是去寻找一些环境变量或者系统设置的函数库所在的目录。否则,dlopen()将按以下顺序搜索函数库文件:1.环境变量LD_LIBRARY指定的路径。2./etc/ld.so.cache中的函数库列表。3./lib目录,然后是/usr/lib。一些非常古老的a.out加载器使用相反的顺序,即先检查/usr/lib,然后再检查/lib。flag的值必须是RTLD_LAZY或RTLD_NOW,RTLD_LAZY表示在执行动态库中的代码时解析未定义的符号,而RTLD_NOW表示在lopen()返回之前解析所有未定义的符号,如果不能bedone则失败。返回值dlopen()函数的返回值是一个句柄,然后后面的函数使用这个句柄进行进一步的操作。如果打开失败,dlopen()返回NULL。如果多次打开一个库,它将返回相同的句柄。如果有多个函数库,并且它们之间存在一些依赖关系,比如X依赖Y,那么需要先加载那些依赖的函数。例如先载入Y,再载入X。dlerror()通过调用dlerror()函数,我们可以得到上一次调用dlopen()、dlsym()或dlclose()的错误信息。dlsym()如果加载了一个DL函数库,不使用,那当然是不可能的。使用DL函数库最重要的函数是dlsym()。在已打开的函数库中搜索该函数。给定的符号。该函数定义如下:void*dlsym(void*handle,char*symbol);参数handle是dlopen打开后返回的句柄,symbol是一个以NIL结尾的字符串。功能:如果dlsym()函数没有找到要查找的符号,则返回NULL。如果知道某个符号的值不能为NULL或者0,那就很好了,可以根据返回结果判断查找到的符号是否存在;但是,如果某个符号的值为NULL,那么这个判断就有问题了。标准的判断方法是先调用dlerror()清除之前可能出现的错误,然后调用dlsym()访问一个符号,再调用dlerror()判断是否有错误发生。dlclose()dlopen()函数的逆过程是dlclose()函数。dlclose()函数强制关闭DL函数库。Dl函数库维护着一个资源使用计数器。当调用dlclose时,此计数器的计数减一。如果计数器为0,则实际释放。实际发布时,如果函数库中有函数_fini(),则会自动调用函数_fini()做一些必要的处理。dlclose()返回0表示成功,其他非零值表示错误。示例#include#includevoidmain(){int(*add)(intx,inty);int(*sub)(intx,inty);void*libptr;libptr=dlopen("./libadd.so",RTLD_LAZY);//加载动态库add=dlsym(libptr,"add");//获取函数地址sub=dlsym(libptr,"sub");printf("add(5,4)is%d\n",add(5,4));printf("sub(5,4)is%d\n",sub(5,4));dlclose(libptr);}四、库二查看命令1.查看依赖库命令ldd使用ldd命令可以查看一个可执行程序依赖了哪些库。这个命令非常有用。在实际工作中,经常会一直使用各种库,一些程序的执行需要依赖几个库。各种库的历史版本很多,经常会出现库不兼容的情况。我们需要根据实际情况,适当降低或升级版本。例如:可以看到线程库libpthread-2.23.so依赖了libc库和ld-linux库。2、nmnm工具可以打印出库中涉及的所有符号。下面是我们创建的动态库libadd.a:nm5.如何安装库安装新库后如何让系统找到它,如下几种方法:1.复制到/lib或/usr/lib如果安装在/lib或者/usr/lib下,那么ld默认就能找到,不需要其他操作。如果安装在其他目录下,需要添加到/etc/ld.so.cache文件中,步骤如下2.通过配置文件/etc/profile永久环境变量设置,编辑/etc/profile.vi/etc/profile在文件末尾添加相应的环境变量信息。动态库环境变量设置:exportLD_LIBRARY_PATH=/home/peng/mylib//home/peng/mylib/指动态库文件夹所在位置。即.so等文件在/home/peng/mylib/下。编辑完成,保存编辑退出;使配置立即生效:source/etc/profile3./etc/ld.so.conf编辑/etc/ld.so.conf文件,添加库文件所在目录的路径vim/etc/ldlocated在.so.conf中添加动态库的路径即可,例如/usr/local/lib/运行ldconfig,此命令将重建/etc/ld.so.cache文件VII.常用库的移植1.jpeg库,使用jpeg图像处理下载地址:http://www.ijg.org/files/解压tarxvzfjpegsrc.v6b.tar.gzcdjpeg-6b生成Makefile./configure--host=arm-linux-gnueabihf--prefix=$PWD/temp_install编译,安装makemakeinstall注意这个库的安装程序有BUG,不会自动创建releaselib,include,man等,所以需要手动创建,或者先做其他库,然后安装这个库mkdir-p/home/peng/jpeg-6b/temp_install/includemkdir-p/home/peng/jpeg-6b/temp_install/libmkdir-p/home/peng/jpeg-6b/temp_install/man/man1》,可通过以下二维码关注,转载请联系易口Linux公众号。