作者:高人arm | 来源:互联网 | 2023-09-25 16:09
目录
一、函数介绍
二、实现热更新
一、函数介绍
动态加载也就是运行时加载,即可以在程序运行时由我们决定何时加载指定的模块。这样进程启动时只加载必要的模块就行,减少了内存占用,除此之外最大的优点是,可以实现在不重启程序的情况下,实现模块的重新加载。这种技术也叫做“热更新”。
先看一下函数原型和功能:
// 按指定的模式打开动态链接库文件,并返回句柄
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的热更新,运行结果如下: