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

dlopen()实现三方库的动态加载

目录一、函数介绍二、实现热更新一、函数介绍动态加载也就是运行时加载,即可以在程序运行时由我们决定何时加载指定的模块。这样进程启动时只加载必要的模块就行,

目录

一、函数介绍

 二、实现热更新




一、函数介绍

动态加载也就是运行时加载,即可以在程序运行时由我们决定何时加载指定的模块。这样进程启动时只加载必要的模块就行,减少了内存占用,除此之外最大的优点是,可以实现在不重启程序的情况下,实现模块的重新加载。这种技术也叫做“热更新”。

先看一下函数原型和功能:

// 按指定的模式打开动态链接库文件,并返回句柄
void *dlopen(const char *filename, int flags);
// 通过句柄获取共享对象或可执行文件中符号的地址
void *dlsym(void *handle, const char *symbol);
// 卸载打开的库
int dlclose(void *handle);

简单的例子实现一下dlopen的动态加载功能:

libcaculator.so动态库的主要方法如下:

int add(int a,int b){return (a + b);}int sub(int a, int b){return (a - b);}int mul(int a, int b){return (a * b);}int div(int a, int b){return (a / b);}

编译打包命令:


gcc -fPIC -shared caculatoe.cc -o libcaculator.so


然后在demo.cc中使用dlopen打开动态库并调用相关函数

#include
#include
#include
//动态链接库路径
#define LIB_CACULATE_PATH "./libcaculator.so"
//函数指针
typedef int (*CAC_FUNC)(int, int);
int main()
{void *handle;char *error;CAC_FUNC cac_func = NULL;//打开动态链接库handle = dlopen(LIB_CACULATE_PATH, RTLD_LAZY);if (!handle) {fprintf(stderr, "%s\n", dlerror());exit(EXIT_FAILURE);}//清除之前存在的错误dlerror();//获取一个函数*(void **) (&cac_func) = dlsym(handle, "add");if ((error = dlerror()) != NULL)  {fprintf(stderr, "%s\n", error);exit(EXIT_FAILURE);}printf("add: %d\n", (*cac_func)(2,7));cac_func = (CAC_FUNC)dlsym(handle, "sub");printf("sub: %d\n", cac_func(9,2));cac_func = (CAC_FUNC)dlsym(handle, "mul");printf("mul: %d\n", cac_func(3,2));cac_func = (CAC_FUNC)dlsym(handle, "div");printf("div: %d\n", cac_func(8,2));//关闭动态链接库dlclose(handle);exit(EXIT_SUCCESS);
}

编译命令:


gcc demo.cc -o demo -ldl


需要链接dl库

编译报错:


./libcaculator.so: undefined symbol: add


 用nm查看libcaculator.so生成的符号如下:

0000000000004020 b completed.8060w __cxa_finalize
0000000000001040 t deregister_tm_clones
00000000000010b0 t __do_global_dtors_aux
0000000000003e88 d __do_global_dtors_aux_fini_array_entry
0000000000004018 d __dso_handle
0000000000003e90 d _DYNAMIC
0000000000001158 t _fini
00000000000010f0 t frame_dummy
0000000000003e80 d __frame_dummy_init_array_entry
0000000000002118 r __FRAME_END__
0000000000004000 d _GLOBAL_OFFSET_TABLE_w __gmon_start__
0000000000002000 r __GNU_EH_FRAME_HDR
0000000000001000 t _initw _ITM_deregisterTMCloneTablew _ITM_registerTMCloneTable
0000000000001070 t register_tm_clones
0000000000004020 d __TMC_END__
00000000000010f9 T _Z3addii
000000000000113e T _Z3divii
0000000000001127 T _Z3mulii
0000000000001111 T _Z3subii

可以看到add方法被编译器重命名为了_Z3addii,这是因为C++代码在编译时,编译器会进行特殊处理,为了支持函数重载,会对函数进行重命名。

解决方法是使用extern C,让编译器以C语言的方式处理代码。

修改caculator.cc文件如下:

#ifdef __cplusplus
extern "C" {
#endifint add(int a,int b){return (a + b);}int sub(int a, int b){return (a - b);}int mul(int a, int b){return (a * b);}int dv(int a, int b){return (a / b);}
#ifdef __cplusplus
}
#endif

然后重新编译运行demo即可


 二、实现热更新

重新修改一下文件实现

demo.h代码如下:

#ifndef DEMO_H
#define DEMO_H
#include
// #include
#include //动态链接库路径
#define LIB_CACULATE_PATH "./libcaculator.so"//函数指针
typedef int (*CAC_FUNC)(int, int);void test(int x);#endif

demo.cc如下:

#include "demo.h"
#include
#include
#include
#include
#include
#include
void *handle;
std::thread th;
std::atomic flag(0);
std::atomic start(false);
int hash_load = 0;
void test (int x) {printf("%d\n", x);
}void load() {char *error;CAC_FUNC cac_func = NULL;struct stat attr;time_t start_time;while(start) {if (stat(LIB_CACULATE_PATH, &attr) == 0 && attr.st_ino != -1) {if (attr.st_mtim.tv_sec != start_time && flag == 1) {printf("start time: %ld, last_time:%ld\n", start_time, attr.st_mtim.tv_sec);printf("need to close first\n");start_time = attr.st_mtim.tv_sec;flag = 0;dlclose(handle);}if (!flag) {printf("need to open\n");//打开动态链接库handle = dlopen(LIB_CACULATE_PATH, RTLD_LAZY);if (!handle) {// fprintf(stderr, "%s\n", dlerror());printf("%s\n", dlerror());continue;}//清除之前存在的错误start_time = attr.st_mtim.tv_sec;dlerror();flag = 1;//获取一个函数*(void **) (&cac_func) = dlsym(handle, "add");if ((error = dlerror()) != NULL) {printf("%s\n", error);continue;}printf("add: %d\n", (*cac_func)(2,7));}}sleep(1);}return ;
}void toload() {start = true;th = std::thread(load);
}
void tounload() {start = false;th.join();printf("unload \n");dlclose(handle);
}
int main()
{toload();sleep(10);tounload();return 0;
}

这时候重新运行demo的可执行文件,然后修改后重新编译libcaculator.so即可实现libcaculator.so的热更新,运行结果如下:


推荐阅读
  • 如何利用正则表达式(regexp)实现高效的模式匹配?本文探讨了正则表达式在编程中的应用,并分析了一个示例程序中存在的问题。通过具体的代码示例,指出该程序在定义和使用正则表达式时的不当之处,旨在帮助读者更好地理解和应用正则表达式技术。 ... [详细]
  • 基址获取与驱动开发:内核中提取ntoskrnl模块的基地址方法解析
    基址获取与驱动开发:内核中提取ntoskrnl模块的基地址方法解析 ... [详细]
  • 本文详细探讨了Zebra路由软件中的线程机制及其实际应用。通过对Zebra线程模型的深入分析,揭示了其在高效处理网络路由任务中的关键作用。文章还介绍了线程同步与通信机制,以及如何通过优化线程管理提升系统性能。此外,结合具体应用场景,展示了Zebra线程机制在复杂网络环境下的优势和灵活性。 ... [详细]
  • Spring框架中的面向切面编程(AOP)技术详解
    面向切面编程(AOP)是Spring框架中的关键技术之一,它通过将横切关注点从业务逻辑中分离出来,实现了代码的模块化和重用。AOP的核心思想是将程序运行过程中需要多次处理的功能(如日志记录、事务管理等)封装成独立的模块,即切面,并在特定的连接点(如方法调用)动态地应用这些切面。这种方式不仅提高了代码的可维护性和可读性,还简化了业务逻辑的实现。Spring AOP利用代理机制,在不修改原有代码的基础上,实现了对目标对象的增强。 ... [详细]
  • 求助高手调试程序,非常感谢您的支持!在编写C语言程序时遇到了一些问题,具体代码如下:```c#include #include #include #define MAX 50int t;```希望有经验的开发者能提供指导,帮助解决调试中的难题。感谢您的时间和帮助! ... [详细]
  • 在PHP的设计中,预定义了9个超级全局变量、8个魔术变量和13个魔术函数,这些变量和函数无需声明即可在脚本的任意位置使用。这些特性在PHP开发中极为常见,能够显著提升开发效率和代码的灵活性。相比之下,Java并没有类似的内置机制,但通过其他方式如上下文对象和反射机制,也可以实现类似的功能。本文将详细探讨这两种语言中这些特殊变量和函数的使用方法及其应用场景。 ... [详细]
  • 在 Windows 10 环境中,通过配置 Visual Studio Code (VSCode) 实现基于 Windows Subsystem for Linux (WSL) 的 C++ 开发,并启用智能代码提示功能。具体步骤包括安装 VSCode 及其相关插件,如 CCIntelliSense、TabNine 和 BracketPairColorizer,确保在 WSL 中顺利进行开发工作。此外,还详细介绍了如何在 Windows 10 中启用和配置 WSL,以实现无缝的跨平台开发体验。 ... [详细]
  • 在CentOS上部署并使用FFmpeg多媒体处理工具
    最近在进行音频处理时需要用到FFmpeg,本文将详细介绍如何在CentOS系统上部署并使用这一强大的多媒体处理工具。首先,从官方网站下载FFmpeg的最新版本,然后通过Xftp工具将下载的压缩包(如ffmpeg-4.3.1.tar.xz)传输到服务器上。接下来,解压文件并按照官方文档进行编译安装。安装完成后,可以通过命令行工具验证FFmpeg是否成功安装,并开始进行多媒体文件的转换和处理。此外,文章还将介绍一些常用的FFmpeg命令和参数,帮助用户快速上手。 ... [详细]
  • 每日精选Codeforces训练题:1119E(贪心算法)、821C(栈模拟)和645D(拓扑排序)
    题目涉及三种不同类型的算法问题:1119E(贪心算法)、821C(栈模拟)和645D(拓扑排序)。其中,1119E的问题背景是有n种不同长度的棍子,长度分别为2^0, 2^1, …, 2^(n-1),每种棍子的数量为a[i]。任务是计算可以组成的三角形数量。根据三角形的性质,任意两边之和必须大于第三边。该问题可以通过贪心算法高效解决,通过合理选择棍子组合来最大化三角形的数量。 ... [详细]
  • GDB 使用心得与技巧总结
    在使用 GDB 进行调试时,可以采用以下技巧提升效率:1. 通过设置 `set print pretty on` 来美化打印输出,使数据结构更加易读;2. 掌握常见数据结构的打印方法,如链表、树等;3. 利用 `info locals` 命令查看当前作用域内的所有局部变量;4. 在需要进行类型强制转换时,正确使用语法,例如 `p (Test::A *) pObj`。这些技巧能够显著提高调试的便捷性和准确性。 ... [详细]
  • 深入解析 ELF 文件格式与静态链接技术
    本文详细探讨了ELF文件格式及其在静态链接过程中的应用。在C/C++代码转化为可执行文件的过程中,需经过预处理、编译、汇编和链接等关键步骤。最终生成的可执行文件不仅包含系统可识别的机器码,还遵循了严格的文件结构规范,以确保其在操作系统中的正确加载和执行。 ... [详细]
  • STAR: 转录组数据分析中的高效比对工具介绍
    欢迎关注“生信修炼手册”!STAR 是一款专为 RNA-seq 数据设计的高效比对工具,以其卓越的速度和高灵敏度著称。该软件在处理大规模转录组数据时表现出色,能够显著提高比对效率和准确性。此外,GATK 推荐使用 STAR 进行预处理步骤,以确保后续分析的可靠性。 ... [详细]
  • 【并发编程】全面解析 Java 内存模型,一篇文章带你彻底掌握
    本文深入解析了 Java 内存模型(JMM),从基础概念到高级特性进行全面讲解,帮助读者彻底掌握 JMM 的核心原理和应用技巧。通过详细分析内存可见性、原子性和有序性等问题,结合实际代码示例,使开发者能够更好地理解和优化多线程并发程序。 ... [详细]
  • 在使用Keil C51创建51单片机项目时,启动代码中包含多个关键元素,这些元素确保了系统的正确初始化和运行。主要包括复位向量、中断向量表、系统时钟配置、寄存器初始化以及主函数入口等。这些组件共同协作,为后续的应用程序执行提供稳定的基础。 ... [详细]
  • Java集合框架特性详解与开发实践笔记
    Java集合框架特性详解与开发实践笔记 ... [详细]
author-avatar
高人arm
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有