以前搞共享库动态加载管理时找的一些资料,放在这里共享。
引言:
在xmeeting中,关于usb手柄部分,采用动态库调用方式,下面翻译一篇David A. Wheeler写的文章。文章就如何创建和使用静态库,共享库以及动如何动态装载库进行了论述。内容纲要如下:
1.概述
2.静态库
3.共享库
3.1 约定
3.2 使用
3.3 环境变量
3.4 创建共享库
3.5 安装与使用
3.6 兼容性
4.动态加载
4.1 dlopen()
4.2 dlerror()
4.3 dlsym()
4.4 dlclose()
4.5 示例
5.辅助知识
5.1 nm命令
5.2 库的构建与析构函数
5.3 脚本
5.4 版本
5.5 GNU libtool
5.6 去除符号空间
5.7 外部执行体
5.8 C++ 与 C
5.9 加速C++初始化
5.10 Linux标准
1.概述
本文就如何在Linux系统中运用GNU工具创建和使用程序库进行论述。所谓"程序库",简单说,就是包含了数据和执行码的文件。其不能单独执行,可以作为其它执行程序的一部分,来完成执行功能。库的存在,可以使得程序模块化,可以加快程序的再编译,可以实现代码重用,可以使得程序便于升级。程序库可分三类:静态库,共享库和动态加载库。
静态库,是在执行程序运行前就已经加入到执行码中,在物理上成为执行程序的一部分;共享库,是在执行程序启动时加载到执行程序中,可以被多个执行程序共享使用。动态加载库,其实并不是一种真正的库类型,应该是一种库的使用技术,应用程序可以在运行过程中随时加载和使用库。
建议库开发人员创建共享库,比较明显的优势在于库是独立的,便于维护和更新;而静态库的更新比较麻烦,一般不做推荐。然而,它们又各有优点,后面会讲到。在C++编程中,要使用动态加载技术,需要参考文章"C++ dlopen MINI-Howto"。
文章中讲述的执行程序和库都采用ELF(Executable and Linking Format)格式,尽管GNU GCC 工具可以处理其它格式,但不在本文的讨论范围。本文可以在 http://www.dwheeler.com/program-library和http://www.linuxdoc.org 找到。
2.静态库
静态库可以认为是一些目标代码的集合。按照习惯,一般以".a"做为文件后缀名。使用ar(archiver)命令可以创建静态库。因为共享库有着更大的优势,静态库已经不被经常使用。但静态库使用简单,仍有使用的余地,并会一直存在。
静态库在应用程序生成时,可以不必再编译,节省再编译时间。但在编译器越来越快的今天,这一点似乎已不重要。如果其他开发人员要使用你的代码,而你又不想给其源码,提供静态库是一种选择。从理论上讲,应用程序使用了静态库,要比使用动态加载库速度快1-5%,但由于莫名的原因,实际上可能并非如此。由此看来,除了使用方便外,静态库可能并非一种好的选择。
要创建一个静态库,或要将目标代码加入到已经存在的静态库中,可以使用以下命令:
ar rcs my_libraty.a file1.o file2.o
以上表示要把目标码file1.o和file2.o加入到静态库my_library.a中。若my_library.a不存在,会自动创建。
静态库创建成功后,需要连接到应用程序中来使用。如果你使用gcc(1)来产生执行程序,需要利用-l选项来指定静态库。更多信息,查看gcc使用手册。
在使用gcc时,要注意其参数的顺序。-l是连接器选项,一定要放在被编译的文件名称之后;若放在文件名称之前,你会连接失败,并会出现莫名其妙的错误。这一点切记。
你也可以直接使用连接器ld(1),使用其选项-l或-L。但最好使用gcc(1),因ld(1)的接口有可能会有变化。
3.共享库
共享库是在程序启动时被装载。当一个应用程序装载了一个共享库后,其它应用程序仍可以装载同一个共享库。基于linux的使用方法,共享库还有其它灵活的而又精妙的特性:
更新库并不影响应用程序使用旧的,非向后兼容的版本;
在执行特定程序时,可以覆盖整个库或更新库中的特定函数;
以上操作不会影响已经运行的程序,他们仍会使用已经装载的库。
3.1约定
要想共享库具有以上特性,一些约定需要遵守。你需要掌握共享库名称之间的区别,特别是搜名(soname)和实名(realname)之间的区别和关系;你还需要知道共享库在文件系统的位置。
3.1.1名称
每个共享库都有一个特定的搜名(soname),其组成如下:
lib + 库名 + .so + . + version
| | |_______________|
前缀 库名 后缀
在文件系统中,搜名是一个指向实名的符号联结。
每个共享库也有一个实名,其真正包含有库的代码,组成如下:
搜名 + . + 子版本号 + . + 发布号
最后的句点和发布号是可选项。
另外,共享库还有一个名称,一般用于编译连接,称为连名(linker name),它可以被看作是没有任何版本号的搜名。
看下面的例子:
lrwxrwxrwx 1 root root libpng.so -> libpng12.so
lrwxrwxrwx 1 root root libpng.so.2 -> libpng.so.2.1.0.12
-rw-r--r-- 1 root root libpng.so.2.1.0.12
在以上信息中, libpng.so.2.1.0.12是共享库的实名(real name),libpng.so.2是共享库搜名(soname),libpng.so则是连接名(linker name),用于编译连接。
3.2共享库的装载
在所有基于GNU glibc的系统(当然包括Linux)中,在启动一个ELF二进制执行程序时,一个特殊的程序"程序装载器"会被自动装载并运行。在linux中,这个程序装载器就是/lib/ld-linux.so.X(X是版本号)。它会查找并装载应用程序所依赖的所有共享库。
被搜索的目录保存在/etc/ls.so.conf文件中,但一般/usr/local/lib并不在搜索之列,至少debian是这样。这似乎是一个系统失误,只好自己加上了。
当然,如果程序的每次启动,都要去搜索一番,势必效率不堪忍受。Linux系统已经考虑这一点,对共享库采用了缓存管理。ldconfig就是实现这一功能的工具,其缺省读取/etc/ld.so.conf文件,对所有共享库按照一定规范建立符号连接,然后将信息写入/etc/ld.so.cache。 /etc/ld.so.cache的存在大大加快了程序的启动速度。
3.3创建共享库
共享库的创建比较简单,基本有两步。首先使用-fPIC或-fpic创建目标文件,PIC或pic表示位置无关代码,然后就可以使用以下格式创建共享库了:
gcc -share _Wl,-soname,your_soname -o library_name file_list library_list
下面是使用a.c和b.c创建库的示例:
gcc -fPIC -g -c -Wall a.c
gcc -fPIC -g -c -Wall b.c
gcc -share -Wl,-soname, libmyab.so.1 -o libmyab.so.1.0.1 a.o b.o -lc
-g表示带有调试信息,-Wall表示产生警告信息。
几个需要注意的地方:
(1)不推荐使用strip处理共享库,最好不要使用-fomit-frame-pointer编译选项
(2)-fPIC和-fpic都可以产生目标独立代码,一般采用-fPIC,尽管其产生的目标文件可能会大些;-fpic产生的代码小,执行速度快,但可能有平台依赖限制。
(3)一般情况下,-Wall,-soname,your_soname编译选项是需要的。当然,-share选项更不能丢。
4 动态加载库
DL技术可以允许应用程序在运行过程的任何时候去加载和使用指定的库。这一技术在插件的实现上很实用。(CJP, alarm ui 使用插件技术。对于这里边的体会还是有一点的。最大的体会就是,要好好利用dlerror(), 如果早使用dlerror(),则不会浪费这么多的时间在编译修改plugin库上面。)动态加载库这一概念并不是着眼于库的文件格式,而是指使用方式。存在着一组接口函数,使得应用程序可以采用DL技术。下面对这些接口函数逐一介绍,在最后给出应用示例。
4.1 dlopen
函数原型:void *dlopen(const char *libname,int flag);
功能描述:dlopen必须在dlerror,dlsym和dlclose之前调用,表示要将库装载到内存,准备使用。如果要装载的库依赖于其它库,必须首先装载依赖库。如果dlopen操作失败,返回NULL值;如果库已经被装载过,则dlopen会返回同样的句柄。
参数中的libname一般是库的全路径,这样dlopen会直接装载该文件;如果只是指定了库名称,在dlopen会按照下面的机制去搜寻:
(1)根据环境变量LD_LIBRARY_PATH查找
(2)根据/etc/ld.so.cache查找
(3)查找依次在/lib和/usr/lib目录查找。
flag参数表示处理未定义函数的方式,可以使用RTLD_LAZY或RTLD_NOW。RTLD_LAZY表示暂时不去处理未定义函数,先把库装载到内存,等用到没定义的函数再说;RTLD_NOW表示马上检查是否存在未定义的函数,若存在,则dlopen以失败告终。(cjp, 如果出错返回了,则可以调用dlerror()查看到具体的原因,这个是非常重要的。)
4.2 dlerror
函数原型:char *dlerror(void);
功能描述:dlerror可以获得最近一次dlopen,dlsym或dlclose操作的错误信息,返回NULL表示无错误。dlerror在返回错误信息的同时,也会清除错误信息。
4.3 dlsym
函数原型:void *dlsym(void *handle,const char *symbol);
功能描述:在dlopen之后,库被装载到内存。dlsym可以获得指定函数(symbol)在内存中的位置(指针)。如果找不到指定函数,则dlsym会返回NULL值。但判断函数是否存在最好的方法是使用dlerror函数,下面是示例:
dlerror();/*清除错误信息*/
function = dlsym(handle,"function_name");
if((error=dlerror()) != NULL)
{
/*错误处理*/
}
else
{
/*找到函数*/
}
4.4 dlclose
函数原型:int dlclose(void *);
功能描述:将已经装载的库句柄减一,如果句柄减至零,则该库会被卸载。如果存在析构函数,则在dlclose之后,析构函数会被调用。
4.5动态加载库示例
#include
#include
#include
int main(int argc,char **argv)
{
void *handle;
double (*cosine)(double);
char *error;
handle = dlopen("/lib/libm.so.6",RTLD_LAZY);
if(!handle)
{
printf("%s/n",dlerror());
exit(1);
}
printf("opened /lib/libm.so.6/n");
cosine = dlsym(handle,"cos");
if((error = dlerror()) != NULL)
{
printf("%s/n",error);
dlclose(handle);
printf("after error,closed /lib/libm.so.6/n");
exit(1);
}
printf("%f/n",(*cosine)(2.0));
dlclose(handle);
printf("closed /lib/libm.so.6/n");
return 0;
}
编译:gcc -o test test.c -ldl。在这个例子中,/lib/libm.so.6是动态加载库,而/usr/lib/libdl.so则是共享库。
5.相关知识
5.1 nm命令
nm(1)命令可以报告库的符号列表,对于查看库的相关信息是一个不错的工具。具体使用查看帮助文档。示例:
$nm -D libavcodec-0.4.7.so | grep 263
结果如下:
00109d40 T h263_encode_mb
00105f94 T h263_encode_picture_header
001a85a0 D h263_encoder
001162d0 T h263_get_picture_format
0010a7b4 T h263_pred_motion
00106df8 T h263_send_video_packet
001ab180 D h263i_decoder
001a85e0 D h263p_encoder
00115c68 T intel_h263_decode_picture_header
……………
其中,T表示正常代码段,D表示初始化数据段
5.2库的构建与析构函数
关于构建与析构函数,一般不需要自己去编程实现。如果你一定要自己做,下面是函数原型:
void __attribute__ ((constructor)) my_init(void);
void __attribute__ ((destructor)) my_fini(void);
在编译共享库时,不能使用"-nonstartfiles"或"-nostdlib"选项,否则,构建与析构函数将不能正常执行(除非你采取一定措施)。
5.3脚本共享库
linux中,共享库可以是脚本形式,当然需要专门的脚本语言。/usr/lib/libc.so是一个典型的例子,内容如下:
/* GNU ld script
Use the shared library, but some functions are only in
the static library, so try that secondarily. */
OUTPUT_FORMAT(elf32-i386)
GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a )
5.4 版本脚本(略)
5.5 GNU libtool(略)
5.6除去记号信息
共享库中的记号信息多为调试之用,但占用了磁盘空间。如果你的库是为嵌入式系统所用,最好去掉记号信息。一种方法,利用strip(1)命令,使用方法查看其帮助文档;另一种方法,使用GNU LD选项-s或-S,例如"-Wl -s"或"-Wl -S"。-S仅除去调试记号信息;-s除去所有记号信息。
5.7编译优化
有一篇文章写的不错"Whirlwind tutorial On Creating really teensy ELF Executables For Linux"。这篇文章中可以说把程序的代码优化到了极点。在我们实际的应用中,可能并需要那些技巧,但通过此文,我们可以更多的了解ELF。
5.8 C++与C
要使得你编写的共享库能同时被C和C++程序使用,库的头文件需要使用"extern C"预定义,下面是一个例子:
#ifndef LIB_HELLO_H
#define LIB_HELLO_H
#ifdef __cplusplus
extern "C"
{
#endif
.....头文件代码
#ifdef __cplusplus
}
#endif
#endif
5.9关于C++程序的启动速度
C++应用程序的启动速度是比较慢的。我一直使用firefox,感受颇深。有人认为这是因主函数启动之前的代码重定位所导致。有一篇文章"making C++ ready for the desktop"(by Waldo Bastian)对这问题作了分析。我读了一下,理解不是很深刻。
5.10 Linux Standard Base(LSB)
LSB是一个项目,致力于制订和推动一系列标准,尽力提高不同Linux发布版本之间的兼容性,从而为应用程序的开发提供一致性的接口。关于linux标准项目的详细信息,可查阅网站www.linuxbase.org。
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1510037
linux 下创建共享库.so
类似Windows系统中的动态链接库,Linux中也有相应的共享库用以支持代码的复用。Windows中为*.dll,而Linux中为*.so,我来详细的告诉你如何在linux下编写动态库,以及如何使用它.
在linux下编写动态链接库的步骤:
1. 编写库的头文件和源文件.
2. 把所有涉及到的源文件用如下方式编译为目标文件:
g++/gcc -g -c -fPIC -o library1.o library1.cpp
g++/gcc -g -c -fPIC -o library2.o library2.cpp
......
......
(注释:-fPIC指通过这个选项来生成与位置无关的代码,可以在任何地址被连接和装载,-c指只编译而不连接原程序)
3. 把所有的目标文件链接为动态库:
g++/gcc -g -shared -Wl,-soname,lib***.so -o lib***.so.1.0.0 library1.o library2.o .... -lc
(注释:-lc选项,表示使用c语言库,一般都要用到)
4. 建立一个库名链接
ln -s lib***.so.1.0.0 lib***.so
现在你就可以引用库了.下面我分别给出简单例子告诉你如何动态和静态使用动态库:
假如你的应用程序源代码叫testlib.cpp
采用/如下方式编译:
g++ -g -o testlib testlib.cpp -ldl
(注释:-ldl选项,表示生成的对象模块需要使用共享库)
////////这个例子告诉你如何动态的调用.so库
testlib.cpp
#include
#include
#include ...
int main()
{
void *handle=NULL;
//define a pointer which will point to the function in the lib you want to use.
YourFuntionType (*pFunc)(YourFunctionPerameterList........);
//open the lib you want to use.
handle=dlopen("/../../../yourlib.so",RTLD_LAZY);
if(handle==NULL)
{
cout<<"failed loading library!"< return -1; } dlerror(); //try to load the function in lib pFunc=(YourFuntionType(*)(YourFunctionPerameterList))dlsym(handle,"YourFuntionName"); if(dlerror()!=NULL) { cout<<"Loading function in lib error!"< return -1; } //now you can use the funtion like this (*pFunc)(YourFuntionPerameterList); return 0; } (注释:dlopen() 第一个参数:指定共享库的名称,将会在下面位置查找指定的共享库。 -环境变量LD_LIBRARY_PATH列出的用分号间隔的所有目录。 -文件/etc/ld.so.cache中找到的库的列表,用ldconfig维护。 -目录usr/lib。 -目录/lib。 -当前目录。(这里就是这种情况) 第二个参数:指定如何打开共享库。 -RTLD_NOW:将共享库中的所有函数加载到内存 -RTLD_LAZY: 会推后共享库中的函数的加载操作,直到调用dlsym()时方加载某函数 dlsym() 调用dlsym时,利用dlopen()返回的共享库的phandle以及函数名称作为参数,返回要加载函数的入口地址。 dlerror() 该函数用于检查调用共享库的相关函数出现的错误。 ) 特别需要注意的几点问题: 1. 当你想用c++写动态库的时候,记住千万别忘了在头文件里面加上如下内容,否则生成的库在动态调用的时候会出问题!!!!!!! #ifdef __cplusplus #endif .... .... #ifdef __cplusplus } #endif 2. 当你的库中包括与omniORB3相关的东西的时候,一定要在makefile中加上 -D__x86__ -D__OSVERSION=4 /////////////这个例子告诉你如何静态调用.so库 首先你得确保你的应用程序能够找到你的.so库,这可以有几种方法来实现. 方法一: 1.你可以把YourLib.so.1.0.0 和YourLib.so放到/usr/lib中,然后执行命令:ldconfig,这样你就可以在你的应用程序中直接调用你库中的函数了,当然你 得把库的头文件包含到你的应用程序中 2.编译你的应用程序 g++/gcc -g -o yourapp yourapp.cpp –lYourLib 方法二: 1.你也可以采用在系统中设置环境变量的办法来实现. 在root目录下: vi .bash_profile 然后添加LD_LIBRARY=/../YourDirIncludingYourLib 然后注消一次,环境变量就生效了,这样你就可以在你的应用程序中直接调用库中的函数了,同样你得有头文件. 2.编译你的应用程序 g++/gcc -g -o yourapp yourapp.cpp –lYourLib 方法三: 你可以直接采用在编译链接的时候告诉系统你的库在什么地方 ///////////////////////////////// 假如你的库中有个函数:int eat(.....) 那么采用如下方式调用它 yourapp.cpp #include "YourLib.h" int main() { eat(); return 0; } 是不是很easy?对了在静态调用的时候好像不存在上面的"注意1"的问题,不过鉴于保险起见,最好还是按照标准的方式写c++头文件吧,这绝对是个好习惯. 一.库的分类 库可以有三种使用的形式:静态、共享和动态。静态库的代码在编译时就已连接到开发人员开发的应用程序中,而共享库只是在程序开始运行时才载入,在编译时,只是简单地指定需要使用的库函数。动态库则是共享库的另一种变化形式。动态库也是在程序运行时载入,但与共享库不同的是,使用的库函数不是在程序运行开始,而是在程序中的语句需要使用该函数时才载入。动态库可以在程序运行期间释放动态库所占用的内存,腾出空间供其它程序使用。由于共享库和动态库并没有在程序中包括库函数的内容,只是包含了对库函数的引用,因此代码的规模比较小。 Linux下的库文件分为共享库和静态库两大类,它们两者的差别仅在程序执行时所需的代码是在运行时动态加载的,还是在编译时静态加载的。区分库类型最好的方法是看它们的文件后缀,通常共享库以.so(Shared Object的缩写)结尾,静态链接库通常以.a结尾(Archive的缩写)。在终端缺省情况下,共享库通常为绿色,而静态库为黑色。 .a的是为了支持较老的a.out格式的可执行文件的 .a是静态库文件,可以用ar 命令生成。 系统中可用的库都存放在/usr/lib和/lib目录中。库文件名由前缀lib和库名以及后缀组成。根据库的类型不同,后缀名也不一样。共享库的后缀名由.so和版本号组成,静态库的后缀名为.a。采用旧的a.out格式的共享库的后缀名为.sa。 这里的name可以是任何字符串,用来唯一标识某个库。该字符串可以是一个单字、几个字符、甚至一个字母。数学共享库的库名为libm.so.5,这里的标识字符为m,版本号为5。libm.a则是静态数学库。X-Windows库名为libX11.so.6,这里使用X11作为库的标识,版本号为6。 三。库操作命令 Linux库操作可以使用命令完成,目前常用的命令是ldd和ldconfig。 1.ldd $ ldd /usr/bin/mesg 2.ldconfig (1)命令格式 (2)主要选项 -f CONF 指定动态链接库的配置文件为CONF,系统默认为/etc/ld.so.conf。 -C CACHE 指定生成的缓存文件为CACHE,系统默认的是/etc/ld.so.cache,文件存放已排好序的可共享的动态链接库的列表。 -p或--print-cache 让ldconfig打印出当前缓存文件所保存的所有共享库的名字。 -r ROOT 改变应用程序的根目录为ROOT。 -n ldconfig仅扫描命令行指定的目录,不扫描默认目录(/lib、/usr/lib),也不扫描配置文件/etc/ld.so.conf所列的目录。 运行没有选项的ldconfig命令时,用于更新高速缓冲文件。这个命令主要用于高速缓冲DNS服务器(Caching DNS Server)。高速缓冲DNS服务器的原理是提供查询的历史记录,并且利用这些记录来提高查询的效率。 当某个查询是第一次被发送到高速缓冲DNS服务器时,高速缓冲DNS服务器就将此查询的整个过程记录下来,在一定的时期内用它来回答所有相同的查询,从而减少整个DNS系统的负担并且提高查询速度。 四。库的升级 Linux系统软件更新很快,新的核心几乎每几个星期就公布一次,其它软件的更新也是非常频繁。多数情况下,盲目跟随潮流的升级并不必要,如果确实需要新版本的特性时再升级。换句话说,不要为了升级而升级。Linux系统中多数软件都是用共享库来编译的,其中包含了在不同程序之间共享的公用子例程。 在运行某个程序时,如果看到如下信息:“Incompatible library version.”则表明需要将该库升级到程序所需要的版本。库是向下兼容的,也就是说,用老版本库编译的程序可以在新安装的版本库上运行,反之则不行。 Linux库函数的升级是一项重要的工作,往往与其它软件包的升级有一定关联作用,所以操作前一定要备份文件。下面看一下如何把Glibc 2.2.4.13升级至2.3.2版本,其过程如下: 1.下载.gz压缩文件并解压 在GUN C网站下载的四个.gz压缩文件,解压至一临时目录中: 2.建立库函数的安装目录 3.建立编译目录 4.编译与安装 5.改变数据库的链接 然后,修改/etc/ld.so.conf,加入一行/usr/higlibc/lib,执行下面代码: 更新/etc/ld.so.cache的内容,列出每个库的版本号,扫描目录和所要创建及更新的链接。 6.更改GCC设置 cd /usr/lib/gcc-lib 7.更新符号链接 8.测试并完成 五。高级共享库特性 共享库的一个非常重要的,也是非常难的概念是 soname——简写共享目标名(short for shared object name)。这是一个为共享库(.so)文件而内嵌在控制数据中的名字。如前面提到的,每一个程序都有一个需要使用的库的清单。这个清单的内容是一系列库的 soname,如同 ldd 显示的那样,共享库装载器必须找到这个清单。 soname 的关键功能是它提供了兼容性的标准。当要升级系统中的一个库时,并且新库的 soname 和老的库的soname 一样,用旧库连接生成的程序,使用新的库依然能正常运行。这个特性使得在 Linux 下,升级使用共享库的程序和定位错误变得十分容易。 在 Linux 中,应用程序通过使用 soname,来指定所希望库的版本。库作者也可以通过保留或者改变 soname 来声明,哪些版本是相互兼容的,这使得程序员摆脱了共享库版本冲突问题的困扰。 查看/usr/local/lib 目录,分析 MiniGUI 的共享库文件之间的关系 2. 共享库装载器 当程序被调用的时候,Linux 共享库装载器(也被称为动态连接器)也自动被调用。它的作用是保证程序所需要的所有适当版本的库都被调入内存。共享库装载器名字是 ld.so 或者是 ld-linux.so,这取决于 Linux libc 的版本,它必须使用一点外部交互,才能完成自己的工作。然而它接受在环境变量和配置文件中的配置信息。 文件 /etc/ld.so.conf 定义了标准系统库的路径。共享库装载器把它作为搜索路径。为了改变这个设置,必须以 root身份运行 ldconfig 工具。这将更新 /etc/ls.so.cache 文件,这个文件其实是装载器内部使用的文件之一。 3. 使用 dlopen 另外一个强大的库函数是 dlopen()。该函数将打开一个新库,并把它装入内存。该函数主要用来加载库中的符号,这些符号在编译的时候是不知道的。比如 Apache Web 服务器利用这个函数在运行过程中加载模块,这为它提供了额外的能力。一个配置文件控制了加载模块的过程。这种机制使得在系统中添加或者删除一个模块时,都不需要重新编译了。 可以在自己的程序中使用 dlopen()。dlopen() 在 dlfcn.h 中定义,并在 dl 库中实现。它需要两个参数:一个文件名和一个标志。文件名可以是我们学习过的库中的 soname。标志指明是否立刻计算库的依赖性。如果设置为RTLD_NOW 的话,则立刻计算;如果设置的是 RTLD_LAZY,则在需要的时候才计算。另外,可以指定RTLD_GLOBAL,它使得那些在以后才加载的库可以获得其中的符号。 当库被装入后,可以把 dlopen() 返回的句柄作为给 dlsym() 的第一个参数,以获得符号在库中的地址。使用这个地址,就可以获得库中特定函数的指针,并且调用装载库中的相应函数。 六、LINUX下动态链接库的使用
extern "C" {
g++/gcc -g -o yourapp yourapp.cpp -L/YourDirIncludingYourLib –lYourLib
有两种说法,如果熟悉WIN平台下的DLL,相信不难理解:
已经开发的大多数库都采取共享库的方式。ELF格式的可执行文件使得共享库能够比较容易地实现,当然使用旧的a.out模式也可以实现库的共享。Linux系统中目前可执行文件的标准格式为ELF格式。
.so的是支持elf格式的可执行文件的库。
.so是动态库文件,编译时加上指定的选项即可生成,具体选项看相应的系统文档了。
二.库的命名规则
GNU库的使用必须遵守Library GNU Public License(LGPL许可协议)。该协议与GNU许可协议略有不同,开发人员可以免费使用GNU库进行软件开发,但必须保证向用户提供所用的库函数的源代码。
libname.so.major.minor
libname.a
ldd是Library Dependency Display缩写,它的作用是显示一个可执行程序必须使用的共享库。
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7eaf000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0xb7feb000)
库安装到系统以后,为了让动态链接库为系统所认识及共享,就需要运行ldconfig。ldconfig命令的用途,主要是在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索出可共享的动态链接库(格式如lib*.so*),进而创建出动态装入程序(ld.so)所需的连接和缓存文件。缓存文件默认为/etc/ld.so.cache,此文件保存已排好序的动态链接库名字列表,ldconfig通常在系统启动时运行,而当用户安装了一个新的动态链接库时,就需要手工运行这个命令。
ldconfig [选项] [libs]
-v或--verbose ldconfig将显示正在扫描的目录、搜索到的动态链接库,以及它所创建的连接的名字。
cd /usr/caolinux
tar xzvf glibc-2.3.2.tar.gz
cd glibc-2.3.2
tar xzvf ../glibc-linuxthreads-2.3.2.tar.gz
tar xzvf ../glibc-crypt-2.3.2.tar.gz
tar xzvf ../glibc-localedata-2.3.2.tar.gz
mkdir /usr/higlibc
cd /usr/higlibc
mkdir cao
cd cao
./configure --enable-add-ons=linuxthreads,crypt,localedata -prefix=/usr/higlibc
make
make check
make install
ln -s /usr/higlibc/lib/ld-linux.so.2 /lib/ld-linux.so.2
ldconfig -v
cp -r i386-redhat-linux higlibc
cd /usr/higlibc/include
ln -s /usr/src/linux/include/linux
ln -s /usr/src/linux/include/asm
ln -s /usr/X11R6/include/X11
1. soname
重要的dlfcn.h头文件
LINUX下使用动态链接库,源程序需要包含dlfcn.h头文件,此文件定义了调用动态链接库的函数的原型。下面详细说明一下这些函数。
1。 dlerror
原型为: const char *dlerror(void);
当动态链接库操作函数执行失败时,dlerror可以返回出错信息,返回值为NULL时表示操作函数执行成功。
2。 dlopen
原型为: void *dlopen (const char *filename, int flag);
dlopen用于打开指定名字(filename)的动态链接库,并返回操作句柄。
filename: 如果名字不以/开头,则非绝对路径名,将按下列先后顺序查找该文件。
(1) 用户环境变量中的LD_LIBRARY值;
(2) 动态链接缓冲文件/etc/ld.so.cache
(3) 目录/lib,/usr/lib
flag表示在什么时候解决未定义的符号(调用)。取值有两个:
1) RTLD_LAZY : 表明在动态链接库的函数代码执行时解决。
2) RTLD_NOW : 表明在dlopen返回前就解决所有未定义的符号,一旦未解决,dlopen将返回错误。
dlopen调用失败时,将返回NULL值,否则返回的是操作句柄。
3。 dlsym : 取函数执行地址
原型为: void *dlsym(void *handle, char *symbol);
dlsym根据动态链接库操作句柄(handle)与符号(symbol),返回符号对应的函数的执行代码地址。由此地址,可以带参数执行相应的函数。
如程序代码: void (*add)(int x,int y); /* 说明一下要调用的动态函数add */
add=dlsym("xxx.so","add"); /* 打开xxx.so共享库,取add函数地址 */
add(89,369); /* 带两个参数89和369调用add函数 */
4。 dlclose : 关闭动态链接库
原型为: int dlclose (void *handle);
dlclose用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。