Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1377764
  • 博文数量: 198
  • 博客积分: 1629
  • 博客等级: 上尉
  • 技术积分: 2743
  • 用 户 组: 普通用户
  • 注册时间: 2011-08-01 15:41
文章分类
文章存档

2023年(6)

2022年(20)

2021年(8)

2020年(3)

2018年(17)

2017年(3)

2016年(3)

2015年(9)

2014年(13)

2013年(17)

2012年(77)

2011年(22)

分类: LINUX

2012-06-18 15:40:04

Linux下使用的函数库分两种静态库和动态库一般分别以后缀.a.so来区别其实就类似于Windows平台下的.lib.dll.

静态库一般是源代码只进行编译后生成的目标文件不需要进行链接直接将该目标文件打包成函数库.对这类静态函数库的使用是在编译链接使用了静态库的源代码文件时指定好静态库文件(目标文件)将这些静态库(目标文件)一起链接进最终的可执行文件中去所以在最终执行程序时静态库中被使用到的函数是随程序启动开始就被加载到内存中去的这中情况其实和我们在编写包含多个cpp文件的程序时一起链接这些cpp文件编译生成的多个.o文件(目标文件)是一样的只不过文件后缀由.o变成了.a

如果需要在自己的代码中使用这类静态库则只要在自己的源代码中include进这些静态库所对应的.h文件(头文件)即可然后在链接的时候(编译的时候有.h文件就够了还用不到静态库文件)指定好所用到的静态库文件就会将这些静态库一起链接进来生成最终的可执行文件

示例

1)首先分别编写Print_s.hPint_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.hlibPrint.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,dlsymdlclose操作的错误信息返回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

阅读(4128) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~