热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

c++函数指针_C/C++动态库的制作与两种使用方式你掌握了吗?

作者:守望,Linux应用开发者,目前在公众号【编程珠玑】分享LinuxCC数据结构与算法工具等原创技术文章和学习资源。前言在《如何制作属
作者:守望,Linux应用开发者,目前在公众号【编程珠玑】 分享Linux/C/C++/数据结构与算法/工具等原创技术文章和学习资源。

前言

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

示例程序

test.c代码如下:

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

test.h代码如下:

#include
void test();

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

制作动态库

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

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

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

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

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

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

使用动态库

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

加载时链接

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

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

编译链接:

$ 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 
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
#include 
#include"test.h"
int main(void){
    printf("start to call test
");
    char *error = NULL;
    /*打开动态库*/
    void *handle = dlopen("libtest.so",RTLD_LAZY);
    if(NULL == handle)
    {
        error = dlerror();
        printf("open error:%s
",error);
        return -1;
    }
    /*返回类型为函数指针*/
    void (*fun)() = dlsym(handle,"test");
    if(NULL == fun)
    {
        error = dlerror();
        printf("open error:%s
",error);
        dlclose(handle);
        return -1;
    }
    /*调用函数*/
    (*fun)();

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

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

  • 使用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
");
}

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

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

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

总结

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

  • 程序运行时不能脱离动态库

  • 动态库有两种常见使用方式,一种是加载是链接,一种是运行时链接

  • 只要函数声明没有改变,动态库中函数实现的更新不需要重新编译可执行文件


●编号541,输入编号直达本文

●输入m获取文章目录

C语言与C++编程

8175f612c314f0dcda8c33e38c1eff4c.png

分享C/C++技术文章




推荐阅读
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了使用kotlin实现动画效果的方法,包括上下移动、放大缩小、旋转等功能。通过代码示例演示了如何使用ObjectAnimator和AnimatorSet来实现动画效果,并提供了实现抖动效果的代码。同时还介绍了如何使用translationY和translationX来实现上下和左右移动的效果。最后还提供了一个anim_small.xml文件的代码示例,可以用来实现放大缩小的效果。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • 本文介绍了一种在PHP中对二维数组根据某个字段进行排序的方法,以年龄字段为例,按照倒序的方式进行排序,并给出了具体的代码实现。 ... [详细]
author-avatar
yvli心语
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有