全部博文(198)
分类: LINUX
2012-06-18 15:40:04
Linux下使用的函数库分两种,静态库和动态库,一般分别以后缀.a和.so来区别,其实就类似于Windows平台下的.lib和.dll.
静态库一般是源代码只进行编译后生成的目标文件,不需要进行链接直接将该目标文件打包成函数库.对这类静态函数库的使用,是在编译链接使用了静态库的源代码文件时,指定好静态库文件(目标文件),将这些静态库(目标文件)一起链接进最终的可执行文件中去。所以在最终执行程序时,静态库中被使用到的函数是随程序启动开始就被加载到内存中去的。这中情况其实和我们在编写包含多个cpp文件的程序时,一起链接这些cpp文件编译生成的多个.o文件(目标文件)是一样的,只不过文件后缀由.o变成了.a。
如果需要在自己的代码中使用这类静态库,则只要在自己的源代码中include进这些静态库所对应的.h文件(头文件)即可,然后在链接的时候(编译的时候有.h文件就够了,还用不到静态库文件)指定好所用到的静态库文件,就会将这些静态库一起链接进来,生成最终的可执行文件。
示例:
1)首先,分别编写Print_s.h和Pint_s.cpp文件,这是静态库的源代码。
//Print_s.h
include
#include
int Print();
下面是Print.cpp.
//Print_s.cpp
#include "Print_s.h"
int Print()
{
printf("TEST!\n");
return 0;
}
2)编完之后,编译出静态库来:
g++ -c -o libPrint.a Print.cpp
这个地方一定要指定-c参数,表示只编译不链接成可执行文件,因为没有main函数,所以不能链接生成可执行文件,链接器会报错。
编译生成了一个名为libPrint.a的目标文件,这个就是我们后面要用的静态库文件。然后,就编写一个使用静态库libPrint.a的程序。
//PrintName_s.h
#include
#include
下面是PrintName_s.cpp.
#include "Print_s.h"
#include "PrintName_s.h"
int main()
{
Print();
printf("Finished execute Print() !\n");
return 0;
}
编完之后。编译PrintName_s.cpp,假设Pirnt_s.h和libPrint.a都放在/opt/temp/下。
g++ -I /opt/temp/ -L /opt/temp/ -lPrint -o PrintName_s.bin PrintName_s.cpp
其中-L /opt/temp/是告诉链接器搜索路径(当然是追加)
-lPrint是给链接器指定库文件名,因为Linux下的库文件名一般都为.a或.so,所以-lPrint其实就是指定名称为libPrint.a或者libPrint.so的库文件名。
或者也可以直接g++ -I /opt/temp/ /opt/temp/libPrint.a -o PrintName_s.bin PrintName_s.cpp
两种方法生成的PrintName_s.cpp大小是一样的.
执行./PrintName_s.bin后输出
TEST!
Finished execute Print() !
动态库是将一组函数编译链接成的一个共享的库文件,使用时,也是需要在编译链接源代码时,指定动态库文件名称,但与使用静态库文件不同,使用动态库进行链接,在链接生成最终的可执行文件的时候,不会将用到的库中的函数直接链接进可执行文件中来,而只是先链接进来一个函数的符号连接,这样,在执行该可执行程序的时候,程序不会在一开始启动的时候就把那些使用到的动态库中的函数加载进来,而是等到具体执行到使用这些函数的代码时才动态加载进来这些库函数,也就是说到用的时候再加载进来。
1)先说动态库的创建.
编写完库函数源代码后(如果是显式地加载动态库的话,源代码的编写会有一些区别,后面会讲到),,需要将源代码编译为一个so动态库文件,以前文中源代码为例,参考以下命令:
g++ -c -o libPrint.o Print_s.cpp
g++ -Wl -shared -fpic -o libPrint.so
-c表示只编译不链接,原因前文已经说过;
-Wl(注意W为大写)表示将这条g++命令的参数传递给链接器,其实也就是说直接进行链接,而跳过编译,所以这个参数也可以理解为指定只进行链接操作;
-shared是一个链接参数,表示将被操作对象链接生成一个共享库(shared object),有了这个参数连接器就不会因为找不到main函数而报错了;
-fpic在这其实是个可选参数,表示生成位置无关的代码(position-independent code),具体含义参见man g++,一般都是在生成动态库时与-shared参数一起使用的;
也可以直接使用下面一条命令来生成libPrint.so.
g++ -shared -fpic -o libPrint.so
这条命令将编译和链接合在了一起,生成的两个so文件大小一样,效果也是一样的.
2)下面看看怎么样在代码中使用动态库:
具体编码中对动态库的使用也分为两种:一种是显示地加载动态库函数;另一种是隐式地加载。
在介绍这两种动态库的使用方法之前,有必要先了解下Linux下对动态库使用的一些机制。
Linux运行的时候,是如何管理共享库(*.so)的?在Linux下面,共享库的寻找和加载是由/lib/ld.so实现的,而ld.so是自动去系统定义的标准路径(/lib,/usr/lib)下寻找应用程序用到的共享库的。
但同时,Linux还提供了一个机制以满足对一些非标准路径下的动态库的使用,这就是ld.so.conf配置文件和ldconfig工具。通用的做法是将你的存放动态库文件的路径添加到/etc/ld.so.conf中去,然后运行ldconfig生成/etc/ld.so.cache文件,ld.so加载动态库的时候,会从ld.so.cache给出的路径中查找,或者也可以直接执行ldconfig $YOUR_LIB_PAHT,强制ld.so搜索指定的路径,不过需要注意,这种方法方法虽然也有效,但效果是暂时的,供程序测试还可以,一旦再度运行ldconfig,则缓存文件内容可能改变,所需的动态链接库可能不被系统共享了。
ld.so.conf配置文件的使用有一个弊端,就是它只支持绝对路径,所以Linux还备有另外一个杀手锏,那就是LD_LIBRARY_PAHT环境变量,ld.so加载共享库的时候,也会查找这个变量所设置的路经。但建议尽量避免使用该环境变量,尤其是作为全局变量。
好了,了解完Linux下动态库使用的一些机制后,再来说动态库使用的两种方法,隐式加载和显式加载。
两种使用方式,不管哪一种,都需要将libPrint.so所在路径加入到ld.so的搜索路径中去,添加方法参照上文。
隐式加载使用库函数,方法比较简单,编码和静态库函数的使用类似,只需要事先在使用该动态库中函数的源文件中包含进该动态库的头文件,即可象使用普通函数一样该动态库中的所有库函数了。编译链接的命令也类似,以前文的源代码为例:
g++ -I /opt/temp/ -L /opt/temp/ -lPrint -o PrintName_s.bin PrintName_s.cpp
显式加载使用动态库,需要系统库函数的支持,所以需要包含进系统头文件dlfcn.h,该头文件中包含有显式加载动态库时需要使用到的一些系统库函数,如dlopen(),dlsym()等。
下面以上文的动态库源代码为例,写一段简单的显式加载动态库程序;
//PrintName_s.cpp
#include "Print_s.h"
#include "PrintName_s.h"
#include
typedef int (* PRINT_FUN)();
int main()
{
void * pSO = dlopen("libPrint.so",RTLD_NOW);
if ( NULL == pSO )
{
printf("Failed to open libPrint.so !\n");
return 1;
}
PRINT_FUN pPrintFun = (PRINT_FUN)(dlsym(pSO, "Print"));
if ( NULL == pPrintFun )
{
printf("Failed to get Print() !\n");
char * pError = dlerror();
printf("%s !\n", pError);
dlclose(pSO);
return 1;
}
int iRT = 1;
iRT = pPrintFun();
if ( 0 != iRT )
{
printf("Failed to execute pPrintFun() !\n");
dlclose(pSO);
return 1;
}
dlclose(pSO);
}
dllope()用来将指定的动态库文件装载到内存中,第一个参数为共享库的名称,由ld.so负责去搜索该名称的动态库文件;第二个参数为打开共享库的方式,有两个取值;
RTLD_NOW,将共享库中的所有函数加载到内存;
RTLD_LAZY,会推后共享库中的函数的加载操作,直到调用dlsym()时方加载某函数。需要注意的是,如果库已经被装载过,则dlopen会返回同样的句柄。
dlsym()用来获取指定名称的函数的地址,返回的是一个void型指针,可以将该地址存放在一个函数指针中,之后就可以通过该函数指针来直接调用该函数指针所指向的动态库函数了。
dlerror()用来获得最近一次dlopen,dlsym或dlclose操作的错误信息,返回NULL表示无错误。dlerror在返回错误信息的同时,也会清除错误信息。
动态库使用完毕之后,还应调用dlclose()将已经装载的库句柄减一,如果句柄减至零,则该库会被卸载。
因为dlopen(),dlsym()都是C语言的库函数,所以通过dlsym()获取到的函数都是C语言格式的,而动态库中的库函数的声明确都是C++格式的,不兼容,这样直接会导致使用该动态库的程序执行时报错。为了使之兼容,所以就需要在libPrint.so的头文件声明中添加extern标识,用于通知C++编译器,按C语言格式来处理以下这些函数的函数名如下:
//Print_s.h
#include
#include
extern "C"
{
int Print();
}
源代码的编译链接也是类似:
g++ -I /opt/temp/ -L /opt/temp/ -lPrint -o PrintName_s.bin PrintName_s.cpp