专栏首页编程珠玑动态库的制作与两种使用方式你掌握了吗?

动态库的制作与两种使用方式你掌握了吗?

前言

在《如何制作属于自己的静态库》中简单介绍了静态库的制作方法,但实际上动态库的使用更为广泛,至于原因,在《静态库和动态库的区别》一文中已有说明。本文介绍动态库的制作方法以及两种使用方式。

示例程序

test.c代码如下:

//来源:公众号【编程珠玑】 网站:https://www.yanbinghu.com
#include"test.h"
void test()
{
    printf("I am test;hello,编程珠玑\n");
}

test.h代码如下:

#include<stdio.h>
void test();

代码比较简单,只有一个test函数,用于打印一段字符串。

制作动态库

只需要执行以下命令即可:

$ gcc test.c -fPIC -shared -o libtest.so

其中的-fPIC表示生成位置无关代码,以便在只有一个副本的情况下供多个应用程序共享

通过readelf命令查看elf文件类型:

$ readelf -h libtest.so
ELF Header:
  Magic:   f  c              
  Class:                             ELF64
  Data:                              's complement, little endian
  Version:                            (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64

从结果中可以看到,libtest.so为Shared object file。

使用动态库

常见有两种使用方式,一种是加载时链接,另一种是使用时链接。

来源:公众号【编程珠玑】 个人博客:https://www.yanbinghu.com 未经授权禁止以任何形式转载

加载时链接

加载时链接在代码中不需要做额外的动作,像使用静态库一样使用即可。例如main.c如下:

//来源:公众号【编程珠玑】 网站:https://www.yanbinghu.com
#include<stdio.h>
#include"test.h"
int main(void)
{
    test();
    return ;
}

编译链接:

$ gcc -o main main.c -L . -ltest

其中-L指定从当前目录下寻找动态库libtest.so,否则会找不到。

然后我们还可以通过ldd命令查看其依赖的动态库:

$ ldd main
    linux-vdso.so.1 =>  (0x00007ffd57757000)
    libtest.so => not found
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f84c13f6000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f84c17c0000)

其中就有我们自己制作的libtest.so。

运行:

$ ./main
./main: error while loading shared libraries: libtest.so: cannot open shared object file: No such file or directory

很不幸,程序并没有如预期的那样运行起来,而是报错了。这是为什么呢?

其实我们在使用ldd命令查看的时候,就注意到:

libtest.so => not found

它并不能找到这个动态库,因为它会默认从系统库的路径去查找这个库,但是我们并没有把这个库放到系统路径下,因此会找不到了。

我们有两种方法解决这个问题:

  • 将libtest.so库放到系统路径下
  • 指定当前进程动态库搜索路径

第一种方法:

$ cp libtest.so /usr/lib
$ ./main
I am test;hello,编程珠玑

第二种方法:

$ export LD_LIBRARY_PATH=./
$ ./main
I am test;hello,编程珠玑

导入LD_LIBRARY_PATH环境变量,指定库搜索路径,使得main程序能够找到libtest.so。

此时再看:

$ ldd main
    linux-vdso.so.1 =>  (0x00007ffcdebdf000)
    libtest.so => ./libtest.so (0x00007f494a45f000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f494a095000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f494a661000)

libtest.so不再是not found了。

使用时链接

为了使用这种方式,需要使用几个函数dlopen,dlsym,dlclose,dlerror,其原型分别如下:

#include <dlfcn.h>
void *dlopen(const char *filename, int flags);
void *dlsym(void *handle, const char *symbol);
int dlclose(void *handle);
char *dlerror(void);

其中dlopen用于打开一个动态库,filename是动态库的名称,flags是打开标志,一般为RTLD_LAZY,表示当要调用的时候才去解析符号;而RTLD_NOW则在dlopen之前就会去解析,还有其他选项这里就不多介绍了。

dlsym函数用于从动态库中查找需要使用的函数;

dlclose函数用于卸载已加载的动态库;

dlerror函数用于打印动态库相关错误。

我们修改main.c,来看看具体如何使用:

//来源:公众号【编程珠玑】 网站:https://www.yanbinghu.com
#include<stdio.h>
#include <dlfcn.h>
#include"test.h"
int main(void)
{
    printf("start to call test\n");
    char *error = NULL;
    /*打开动态库*/
    void *handle = dlopen("libtest.so",RTLD_LAZY);
    if(NULL == handle)
    {
        error = dlerror();
        printf("open error:%s\n",error);
        return -1;
    }
    /*返回类型为函数指针*/
    void (*fun)() = dlsym(handle,"test");
    if(NULL == fun)
    {
        error = dlerror();
        printf("open error:%s\n",error);
        dlclose(handle);
        return -1;
    }
    /*调用函数*/
    (*fun)();

    /*关闭*/
    dlclose(handle);
    printf("end to call test\n");
    return ;
}

这种方式的动态库使用可以大致分为以下几个步骤:

  • 使用dlopen打开动态库
  • 使用dlsym找到需要使用的符号
  • 调用动态库中的函数
  • dlopen关闭(卸载)动态库

在文本的代码中,用到了函数指针,相关内容可参考《高级指针话题-函数指针》。 编译运行:

$ gcc  main.c -ldl -L . -o main  #需要链接libdl.so库
$ ./main 
start to call test
open error:libtest.so: cannot open shared object file: No such file or directory

运行时,我们发现并没有如预期的那样。但是可以看到,程序已经打印了start to call test,然后才报错,说明程序是在运行起来之后再尝试去从动态库中查找test符号的。

当然了,至于问题原因,我们在前面已经提到了,是由于没有设置动态库搜索路径或者在系统默认库路径下没有我们需要的libtest.so。按照前面提供的两种方法修正后,重新运行:

$ ./main
start to call test
I am test;hello,编程珠玑
end to call test

一切正常。

这种方式有以下好处:

  • 编译时无需链接需要的动态库,我们注意到第二种方式编译时没有加-ltest
  • 如果程序的某些场景不需要动态库的函数,那么它就不会去加载该动态库

再看动态库

如果我们修改test.c的代码,我们不再需要重新编译main.c,而只需要更新动态库即可。 假如我们将test.c代码中的打印语句修改为如下:

//来源:公众号【编程珠玑】 网站:https://www.yanbinghu.com
#include"test.h"
void test()
{
    printf("hello www.yanbinghu.com\n");
}

重新生成动态库libtest.so,重新执行main:

$ ./main
start to call test
hello www.yanbinghu.com
end to call test

我们发现,不需要重新编译main进程,就可以达到替换test函数实现的效果

总结

动态库应用广泛,其制作过程可能不作深入要求,但是其基本使用还是非常有必要了解的。本文总结如下:

  • 程序运行时不能脱离动态库
  • 动态库有两种常见使用方式,一种是加载是链接,一种是运行时链接
  • 只要函数声明没有改变,动态库中函数实现的更新不需要重新编译可执行文件

本文分享自微信公众号 - 编程珠玑(shouwangxiansheng),作者:守望先生

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-07-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 记64位地址截断引发的挂死问题

    最近要将整个项目的代码从原先的只支持32位变成同时支持32位和64位,这个过程中遇到一个很不容易定位的挂死问题,花了不少时间才定位解决,因此分享给大家。

    编程珠玑
  • find命令高级用法

    在《Linux中的文件查找技巧》一文中,我们已经知道了文件查找的基本方法,今天我们介绍find命令的一些高级使用技巧。它能满足我们一些更加复杂的需求。

    编程珠玑
  • 怎么正经的实现shell脚本单例运行?

    一个非常简单的思路就是,新的脚本被执行时,先检测当前脚本是否有其他实例正在运行,如果有则直接退出。

    编程珠玑
  • Nvidia开放Clara医疗保健平台和医疗成像AI工具,并宣布新的合作伙伴关系

    医疗保健仍然是AI应用和服务增长最快的市场之一,预计到2021年总体价值将达到66亿美元。AI系统可以分析超声波扫描,检测眼部疾病,并加快X射线和计算机断层扫描...

    AiTechYun
  • 千里之行,始于足下变量字符串

    罗罗攀
  • 使用 AngularJS 的 $resource 连接 WebAPI Controller

    ASP.NET Web API 是 .NET 平台创建 REST 风格的 HTTP 服务的理想框架, REST 风格的 HTTP 服务可以被多种客户端使用, 包...

    beginor
  • 2019年末,10 位院士对 AI 的深度把脉(下)

    2019 年 12 月 20 日,由鹏城实验室、新一代人工智能产业技术创新战略联盟主办的为期两天的「新一代人工智能院士高峰论坛」在深圳开幕。

    AI科技评论
  • 北京出生,四川长大,美国打工,这位带领谷歌重返中国的小姐姐苦

    曾经有人问了这么一个问题:"谷歌做过让你觉得最厌恶的事情是什么?",点赞数最高的回答是“退出中国大陆市场”。是的,自从2010年谷歌退出中国到现在,已经七年多了...

    企鹅号小编
  • 你真的会写mybatis.xml吗?养成好习惯写养眼好用的xml

    我们使用mybatis无非就是进行一些增删改查的操作,但是简单的增删改查想要写好却大有门道。

    我的小熊不见了丶

扫码关注云+社区

领取腾讯云代金券