uc_02_库_动态库的动态加载

一、前言

1.4  calc.h  calc.c  show.h  show.c  math.h

二、静态库的制作和使用

2.0  静态库

2.1  编辑库的实现代码和接口声明:

2.2  编译成目标文件

2.3  打包成静态库

2.4  库的使用代码main.c

2.5  编译和链接静态库(得到可执行文件main):

2.6  执行程序

三、动态库的制作和使用

3.0  动态库

3.1  编辑库的实现代码和接口声明:

3.2  编译成目标文件

3.3  打包成动态库

3.3*  PIC(动态库必备)

3.4  库的使用代码main.c:

3.5  编译和链接动态库(得到可执行文件main):

3.6  执行程序(LD_LIBRARY_PATH)

3.6.*  LD_LIBRARY_PATH 等3个环境变量

四、动态库的动态加载

4.0  理论

4.1  dlopen()

4.2  dlsym()

4.3  dlclose()

4.4  dlerror()

4.5  load.c代码

4.5*  (*add) 和 *add

4.6  nm命令

4.7  ldd命令


一、前言

1.0  .o  .a  .so 目标文件、静态库文件、动态库文件均为二进制文件

1.1  为何要把一个程序分成多个源文件,并由每个.c源文件编译生成独立的.o目标文件?

        化整为零、易于维护、便于协作。

1.2  为何要把多个.o目标文件合并成一个.a或.so库文件?

        集零为整、方便易用、易于复用。

1.3  可以把库文件看成一种代码仓库,它提供给user一些可以直接拿来用的变量、函数或类。

1.4  calc.h  calc.c  show.h  show.c  math.h

//calc.h  计算模块头文件
#ifndef __CALC_H__
#define __CALC_H__
//加法计算
int add(int,int);

//减法计算
int sub(int,int);
#endif //__CALC_H__
//show.h  显示模块头文件
#ifndef __SHOW_H__
#define __SHOW_H__
//显示等式  3 + 5 = 8    4 - 2 = 1
void show(int,char,int,int);
#endif //__SHOW_H__
//math .h  接口文件
#ifndef __MATH_H__
#define __MATH_H__
#include"calc.h"
#include"show.h"

#endif //__MATH_H__
//calc.c  计算模块实现
#include"calc.h"

//加法函数
int add(int a,int b){
    return a + b;
}

//减法计算
int sub(int a,int b){
    return a - b;
}
//show.c  显示模块实现
#include<stdio.h>
#include"show.h"

//显示等式
void show(int l,char op,int r,int res){
    printf("%d %c %d = %d\n",l,op,r,res);
}

二、静态库的制作和使用

2.0  静态库

        静态库的本质就是将多个.o目标文件打包成一个文件。

        链接静态库的过程就是将库中被调用的代码复制到调用模块中。

        静态库名:libxxxx.a

        

以构建数学库为例,libmath.a静态库的构建顺序如下:

2.1  编辑库的实现代码和接口声明:

        计算模块:calc.h  calc.c

        显示模块:show.h  show.c

        接口文件:math.h

2.2  编译成目标文件

        gcc -c calc.c

        gcc -c show.c

2.3  打包成静态库

ar -r libmath.a calc.o show.o

        ar [选项] <静态库文件> <目标文件列表>

                -r 将目标插入到静态库库中,已存在则更新

                -q 将目标文件追加到静态库尾

                -d 从静态库中删除目标文件

                -t 列表显示静态库中的目标文件

                -x 将静态库展开为目标文件

2.4  库的使用代码main.c

2.5  编译和链接静态库(得到可执行文件main):

//编译并链接相应库,链接方式有3种

$gcc main.c libmath.a -o main    //库的使用文件  和  库文件  同路径

$gcc main.c -lmath -L../mylibss -o main    //-l接库小名,没有lib和.a   -L接路径   

$export LIBRARY_PATH=$LIBRARY_PATH:../mylibss
$gcc main.c -lmath -o main    //路径添加到环境变量里了,无需-L路径

2.6  执行程序

        执行程序前,可删除静态库文件。

$./main
3 + 5 = 8
3 - 5 = -2
$

三、动态库的制作和使用

3.0  动态库

        动态库和静态库不同,链接动态库不需要将被调用的函数代码(.a)复制到包含调用代码(main.c)的可执行文件(main)中,相反,链接器会在调用语句处嵌入一段指令,在该程序执行到这段指令时,会加载该动态库并寻找被调用函数的入口地址并执行之。

        如果动态库中的代码同时为多个进程所用,动态库在内存中的实例仅存一份,为所有使用该库的进程所共享(必须-fpic),因此动态库也称共享库

动态库名:libxxxx.so  shared object)

        

        

3.1  编辑库的实现代码和接口声明:

        计算模块:calc.h  calc.c

        显示模块:show.h  show.c

        接口文件:math.h

3.2  编译成目标文件

        gcc -c -fpic calc.c

        gcc -c -fpic show.c

3.3  打包成动态库

        gcc -shared calc.o show.o -o libmath.so

        2+3可同时执行:

        gcc -shared -fpic calc.c show.c -o libmath.so

3.3*  PIC(动态库必备)

        Position Independent Code, 位置无关码

        调用代码通过相对地址标识调用代码的位置,

        模块中的指令与该模块被加载到内存中的位置无关。

        -fPIC    大模式:生成代码大,运行速度慢,支持所有平台

        -fpic     小模式:生成代码小,运行速度快,支持部分平台

3.4  库的使用代码main.c:

3.5  编译和链接动态库(得到可执行文件main):

//编译并链接相应库,链接方式有3种

$gcc main.c libmath.so -o main    //库的使用文件  和  库文件  同路径

$gcc main.c -lmath -L../mylibss -o main    //-l接库小名,没有lib和.a   -L接路径   

$export LIBRARY_PATH=$LIBRARY_PATH:../mylibss
$gcc main.c -lmath -o main    //路径添加到环境变量里了,无需-L路径

3.6  执行程序(LD_LIBRARY_PATH)

       在可执行程序的链接阶段,不将所调用函数的二进制代码复制到可执行程序中,而只将该函数在共享库中的地址嵌入到调用模块中,因此运行时需要依赖共享库,不能删除,也需要知道共享库的路径。

        通过LD_LIBRARY_PATH环境变量中包含共享库所在的路径,以告知链接器在运行时链接动态库。代码如下第1行。

$export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:../mylibss
$./main
3 + 5 = 8
3 - 5 = -2
$

3.6.*  LD_LIBRARY_PATH 等3个环境变量

PATH        //用于bash敲命令时,找命令

LIBRARY_PATH        //用于gcc编译(sel和链接库)时,找库

LD_LIBRARY_PATH        //用于链接器在运行程序时找动态

四、动态库的动态加载

4.0  理论

        在程序执行的过程中,开发人员可以动态加载共享库(什么时候用什么时候加载),以提高内存利用效率。

        在程序中动态加载共享库需要调用一组函数,他们位于该头文件中  #include<dlfcn.h>

        并链接该库    -ldl                // libdl.so

4.1  dlopen()

        void* dlopen(char const* filename, int flag);

        -功能:将共享库载入内存,并过得其访问句柄

        -参数:

         filename    动态库路径,若只给文件名不给路径,则根据LD_LIBRARY_PATH环境变量搜索

         flag            加载方式宏,可取以下值:

                           RTLD_LAZY  延迟加载,使用动态库中的符号时在真正加载到内存

                           RTLD_NOW  立即加载

        -返回值:成功返回动态库的访问句柄,失败返NULL

        -句柄:唯一标识了系统内核所维护的共享库对象,将作为后续函数调用的参数。

4.2  dlsym()

        void* dlsym(void *handle, char const* symbol);

        -功能:从已被加载的动态库中获取特定名称的符号的地址

        -参数:handle  动态库访问句柄

                    symbol  符号名

        -返回值:成功返回给定符号的地址,失败返NULL

        -该函数所返回的指针为void*类型,需要造型为与实际目标类型相一致的指针,才能使用。见           代码。

4.3  dlclose()

        int dlclose(void* handle);

        -功能:从内存中卸载动态库

        -参数:handle  动态库句柄

        -返回值:成功0,失败非0

        -所卸载的共享库未必真的会从内存中立即消失,因为其他程序可能还需要使用该库呢

        -只有所有使用该库的程序都显示或隐式地卸载了该库,库占内存空间才会真正释放

        -无论所卸载的共享库是否真正被释放,传递给dlclose函数的句柄都会在该函数成功返回后立           即失效

4.4  dlerror()

        char* dlerror(void);

        -功能:获取在加载、使用和卸载共享库过程中所发生的错误

        -返回值:有错误则返回指向错误信息字符串的指针,否则返NULL

        -为什么不用perror()

                 perror()用于系统调用函数、库函数

                 dlerror()专用于<dlfcn.h>,动态加载

4.5  load.c代码

//load.c  动态库的动态加载
#include<stdio.h>
#include<dlfcn.h>// dlopen() dlsym() dlclose() dlerror()

int main(void){
    //加载库到内存
    void* handle = dlopen("/home/tarena/2308/uc/day02/shared/libmath.so",RTLD_NOW);
    if(handle == NULL){
        fprintf(stderr,"dlopen:%s\n",dlerror());
        //printf("dlopen:%s\n",dlerror());
        return -1;
    }
    //获取函数的地址
    int (*add)(int,int) = dlsym(handle,"add");  //注意int (*add) 与 int* add
    if(add == NULL){
        fprintf(stderr,"dlsym:%s\n",dlerror());
        return -1;
    }
    int (*sub)(int,int) = dlsym(handle,"sub");
    if(sub == NULL){
        fprintf(stderr,"dlsym:%s\n",dlerror());
        return -1;
    }
    void (*show)(int,char,int,int) = dlsym(handle,"show");
    if(show == NULL){
        fprintf(stderr,"dlsym:%s\n",dlerror());
        return -1;
    }
    //使用函数
    int a = 3,b = 5;
    show(a,'+',b,add(a,b));
    show(a,'-',b,sub(a,b));
    //卸载库
    dlclose(handle);
    return 0;
}

4.5*  (*add) 和 *add

        int (*add)(int,int)

        add是一个指针名字,精确说是函数指针名,该指针指向的是函数地址。函数没名,形参是 int,int,函数返回int。

        int *add(int,int)

        add是函数名,该函数形参是int,int,函数返回一个int*类型的指针。

4.6  nm命令

       .o  .a  .so 目标文件、静态库文件、动态库文件均为二进制文件,无法直接查看。

        可用nm命令来列出目标文件、可执行程序、静态库或共享库中的符号(如函数名)。

$nm libmath.a  //查看库中的函数名

show.o:
                 U printf
0000000000000000 T show

calc.o:
0000000000000000 T add
0000000000000014 T sub
$

4.7  ldd命令

        查看可执行文件或共享库所依赖的共享库

$ldd a.out

        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值