动态库和静态库

很多时候我们需要多次调用一些函数,为了方便我们需要把这写函数封装在一个库里面,以便在以后主函数链接生成可执行文件的时候调用。因为是在主函数连接的时候利用,所以需要打包生成库文件的文件都必须是已经编译过的目标文件(即.o文件)。

静态库:
    四个数学函数分别在不同的文件里面,但他们包含在一个头文件下面:
    math.h:

    #ifndef MATH_H_
    #define MATH_H_
    
    #include <stdio.h>
    
    typedef int(* funp)(int ,int );
    
    int add(int x,int y);
    int sub(int x,int y);
    int mul(int x,int y);
    int div(int x,int y);

    #enfif


函数文件分别为:
    add.c

    #include "math.h"

    int add(int x,int y)
    {
        return x+y;

    }

其余同理。

主函数:
    #include "math.h"

    void callback(fun p,int x,int y,char *fname)
    {
        printf("%s=%d\n",fname,p(x,y));
    }
    
    int main(void)
    {
        int a=9,b=3;
        callback(add,a,b,"add");
        callback(sub,a,b,"sub");
        callback(mul,a,b,"mul");
        callback(div,a,b,"div");
    
        return 0;
    }


现在需要将数学函数都打包生成一个库函数,以便主函数直接调用。
首先编译数学函数,得到目标文件,然后打包生成静态库文件。
    步骤如下:
1:文件预处理:gcc -E add.c -o test.i
.i文件中存放着.c预处理之后的代码,如果不加 -o test.i 的话不会输出预处理结果,    看不到.i文件。
2:文件编译:gcc -S add.i -o add.s
预处理之后,可直接对生成的.i文件编译,生成汇编代码:
3:汇编:gcc -c add.s -o add.o
汇编器将汇编代码编译为目标文件.o

/*因为我们的目的是打包生成库函数,是供主函数调用时链接生成可执行文件的,所以只要生成目标文件就可以了。但是主函数要生成可执行文件,除了上面3个步骤外,还需要一个链接的过程。就是将主函数生成的目标文件和附加的目标文件链接起来,最终生成可执行文件。例如:gcc - main.o main */

当生成所有需要的.o文件后,就需要打包生成静态库文件了
在命令行中输入:
ar rs libmymath.a add.o sub.o mul.o div.o
库文件名都是以lib开头的,静态库以.a作为后缀(archive),ar命令类似以tar命令,用来打包的,但是把目标文件打包生成静态库文件只能用ar命令。选项r表示将后面的文件列表添加到文件包.a当中,如果.a不存在就创建,存在就更新。s表示为静态库创建索引,给链接器使用。ranlib也可以为静态可创建索引,上面命令等价于:
ar r libmymath.a add.o sub.o mul.o div.o
ranlib libmymath.a
系统就会提示创建一个静态库文件。这样静态库就做好了。


然后我们编译主函数的时候把库文件链接上:
gcc main.c -L库文件路径 -l库文件名(文件名数除去前后缀的部分,本例中就是mymath) -I头文件路径 -o main

-L选项告诉编译器到哪里寻找库文件,-l告诉编译器要连接哪个库,-I告诉编译器去哪里寻找头文件。注意,即使库文件就在当前目录,编译器也不会去找,所以-L选项不能少。

由于利用指令的方法创建库函数很麻烦,下面介绍一种简单的方法:
用Makefile创建静态库函数

将静态库函数需要包含的目标文件的源文件.c放到一个文件夹当中,在此文件夹中创建Makefile文件(vim Makefile)

#Makefile

libmymath.a:*.c
    gcc -c *.c
    ar rs libmymath.a *.o

clean:
    @echo "cleanning project"
    -rm *.o
    -rm *.a
    @echo "clean completed"

.PHONY :clean

由于Makefile的隐含规则中可以默认当没有源文件时自动生成源文件,所以Makefile可以简化为
#Makefile

libmymath.a:*.o
    ar rs libmymath.a *.o

clean:
    @echo "cleanning project"
    -rm *.o
    -rm *.a
    @echo "clean completed"

.PHONY :clean

当在当前路径下的命令行中输入make时就可创建静态库函数,同理可以再主函数文件夹下面写一个Makefile,也可以完成。
    




动态库:

生成动态库也需要目标文件,但是和一般地目标文件有所不同(具体原因待补),在编译时需要加-fPIC选项,表示-f后面编译的选项生成的是位置无关代码。
例如:
gcc -c -fPIC add.c sub.c mul.c div.c
这样生成目标文件了,之在打包生成动态库:
gcc -shared -o libmymath.so *.o
这样就生成了动态库了。

这样可以一步完成:gcc -shared -fPIC -o libmymath.so  *.c


现在把main.c和共享库编译连接在一起:
gcc main.c -L库文件位置 -l库文件名 -I头文件位置 -o main
然后运行:
./main
结果出乎意料,编译时没问题,由于指定了-L选项,编译器可以找到库文件,而运行时却说找不到。那么运行时在哪些路径下找共享库呢?可以用ldd命令查看:
ldd mian

共享库路径的搜索顺序:
1,在环境变量LD_LIBRARY_PATH保存的路径中查找。
2,然后从缓存文件/etc/ld.so.cache中查找。
3,如果上述步骤都没找到,则到默认的系统库文件目录中查找,先是/lib然后是/usr/lib。

既然如此,我们在运行程序时修改环境变量,把共享库所在的目录添加到搜索路径:
LD_LIBRARY_PATH=/home/...(库文件的路径)  ./main
也可以用这样两条命令:
export LD_LIBRARY_PATH=/home/...(库文件路径,可以用$PWD表示在当前路径)
./main
第一条命令在当前shell进程中设置一个环境变量,一旦在shell进程中设置了环境变量,以后每次执行时shell都会把自己的环境变量传给新创建的进程,所以第二条命令创建的进程main就会获得这个环境变量。但是在关闭shell,重启一遍
terminal时这个新设置的环境变量就无效了。注意这两种执行方式的区别,第一种 LD_LIBRARY_PATH=/home/...(库文件的路径)  ./main 命令室友当前创建的main进程才获得这个环境变量,shell本身不保存,以后执行其他命令也不会获得它,所以重新执行./main时找不到共享库,但是第二种方法在不关闭shell的前提下重新执行./main是有效的。

另外一种方法就是把libmymath.so 考到/usr/lib或者/lib目录,这样可以确保动态链接器能找到这个共享库


用Makefile的方法完成共享库的创建和链接:

#makefile

libmymath.so;*.c
    gcc -c *.c -fPIC
    gcc -shared -o libmymath.so *.o

install:
    sudo cp libmymath.so /usr/lib
remove:
    sudo rm /usr/lib/libmymath.so

clean:
    @echo "clean project"
    -rm *.o
    @echo "clean completed"
distclean:
    @echo "clean project"
    -rm libmymath.so
    @echo "clean completed"

.PHONY:clean
.PHONY:distclean

利用隐含规则可以简化:
#makefile
CFLAGS=--fPIC

libmymath.so;*.o
    gcc -shared -o libmymath.so *.o

install:
    sudo cp libmymath.so /usr/lib
remove:
    sudo rm /usr/lib/libmymath.so

clean:
    @echo "clean project"
    -rm *.o
    @echo "clean completed"
distclean:
    @echo "clean project"
    -rm libmymath.so
    @echo "clean completed"

.PHONY:clean
.PHONY:distclean

在主函数文件夹里面写下Makefile:
#Makefile
main:mian.o
    make -C 动态库文件的路径
    gcc main.c -L动态库文件路径 -l动态库文件名 -I头文件路径 --o main

    clean:
    @echo "clean project"
    -rm *.o
    @echo "clean completed"

distclean:
    @echo "clean project"
    -rm main
    @echo "clean completed"

这样在在库文件目录下make,然后make install安装完成后,在主函数文件目录下make命令就生成了main可执行文件。然后在库文件目录下make remove即可卸载安装。

 

 

 

1,使用dlopen()打开指定的动态链接库文件,

2,使用dlsym()查找库文件中的函数,并赋值给一个函数指针变量,

3,使用函数指针变量运行对应的函数,

4,使用dlclose关闭打开的动态链接库文件,

编译命令:gcc -o testdlso testdlso.c -ldl

运行:  ./testdlso fun.so

 http://www.cnblogs.com/Anker/p/3746802.html

dlopen

基本定义

  功能:打开一个动态链接库 
  包含头文件: 
  #include <dlfcn.h> 
  函数定义: 
  void * dlopen( const char * pathname, int mode ); 
  函数描述: 
  在dlopen的()函数以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程。使用dlclose()来卸载打开的库。 
  mode:分为这两种 
  RTLD_LAZY 暂缓决定,等有需要时再解出符号 
  RTLD_NOW 立即决定,返回前解除所有未决定的符号。 
  RTLD_LOCAL 
  RTLD_GLOBAL 允许导出符号 
  RTLD_GROUP 
  RTLD_WORLD 
  返回值: 
  打开错误返回NULL 
  成功,返回库引用,指向动态库文件的void指针
  编译时候要加入 -ldl (指定dl库) 
  例如 
  gcc test.c -o test -ldl
编辑本段
使用 dlopen
   dlopen()是一个强大的库函数。该函数将打开一个新库,并把它装入内存。该函数主要用来加载库中的符号,这些符号在编译的时候是不知道的。比如 Apache Web 服务器利用这个函数在运行过程中加载模块,这为它提供了额外的能力。一个配置文件控制了加载模块的过程。这种机制使得在系统中添加或者删除一个模块时,都 不需要重新编译了。 
  可以在自己的程序中使用 dlopen()。dlopen() 在 dlfcn.h 中定义,并在 dl 库中实现。它需要两个参数:一个文件名和一个标志。文件名可以是我们学习过的库中的 soname。标志指明是否立刻计算库的依赖性。如果设置为 RTLD_NOW 的话,则立刻计算;如果设置的是 RTLD_LAZY,则在需要的时候才计算。另外,可以指定 RTLD_GLOBAL,它使得那些在以后才加载的库可以获得其中的符号。 

  当库被装入后,可以把 dlopen() 返回的句柄作为给 dlsym() 的第一个参数,以获得符号在库中的地址。使用这个地址,就可以获得库中特定函数的指针,并且调用装载库中的相应函数。

 

NOTE: 如果pathname是NULL, 则返回的是调用dlopen的模块的库引用(handle)。 例如: 如果我们在我们的APP里面调用dlopen去动态load一个.so, 但是pathname传入的是NULL, 则dlopen最后返回的应该是该APP的引用(handle)

Additional link: http://tldp.org/HOWTO/Program-Library-HOWTO/dl-libraries.html

--------------------------------------------------------------------------------------------------------------------------

dlsym
   
  dlsym()的函数原型是 
  void* dlsym(void* handle,const char* symbol) 
  该函数在<dlfcn.h>文件中。 
  handle是由dlopen打开动态链接库后返回的指针,symbol就是要求获取的函数的名称,函数返回值是void*,指向函数的地址,供调用使用

取动态对象地址:
#include <dlfcn.h>
void *dlsym(void *pHandle, char *symbol);
dlsym根据动态链接库操作句柄(pHandle)与符号(symbol),返回符号对应的地址。
使用这个函数不但可以获取函数地址,也可以获取变量地址。比如,假设在so中
定义了一个void mytest()函数,那在使用so时先声明一个函数指针:
void (*pMytest)(),然后使用dlsym函数将函数指针pMytest指向mytest函数,
pMytest = (void (*)())dlsym(pHandle, "mytest");

--------------------------------------------------------------------------------------------------------------------------

dlclose
  dlclose() 
  包含头文件: 
  #include <dlfcn.h> 
  函数原型为: 
  int dlclose (void *handle); 
  函数描述: 
   dlclose用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。

--------------------------------------------------------------------------------------------------------------------------

dlerror
  dlerror() 
  包含头文件: 
  #include <dlfcn.h> 
  函数原型: 
  const char *dlerror(void); 
  函数描述: 
  当动态链接库操作函数执行失败时,dlerror可以返回出错信息,返回值为NULL时表示操作函数执行成功。

LINUX创建与使用动态链接库并不是一件难事。
  编译函数源程序时选用-shared选项即可创建动态链接库,注意应以.so后缀命名,最好放到公用库目录(如/lib,/usr/lib等)下面,并要写好用户接口文件,以便其它用户共享。
  使用动态链接库,源程序中要包含dlfcn.h头文件,写程序时注意dlopen等函数的正确调用,编译时要采用-rdynamic选项与-ldl选项 ,以产生可调用动态链接库的执行代码

 

EXAMPLE

#include <stdio.h>

int add(int a, int b)
{
        return a+b;
}
int sub(int a, int b)
{
        return a-b;
}

 

#include <stdio.h>
#include <dlfcn.h>

typedef int (* funp)(int ,int );

int main(void)
{
        void *handle;                                                             /*用来标定库文件加载的内存位置*/

        handle = dlopen("./libmath.so", RTLD_LAZY);      /*根据库文件的路径加载库文件到内存中*/
        if(!handle){
                dlerror();
        }

        funp fp  = (int (*)(int ,int ))dlsym(handle, "add");    /*根据函数名符号找到需要动态加载的函数位置*/
        if(!fp){
                dlerror();
        }
        printf("add = %d\n",fp(3,4));

        if(dlclose(handle)){                    /*从内存中卸载动态库文件*/
                dlerror();
        }

        return 0;
}

 

编译:gcc main.c -ldl

注意:事先要把动态库文件准备好。

 

 

 

posted @ 2014-06-01 19:59  一语天然  阅读(202)  评论(0编辑  收藏  举报