目录
一、函数介绍
动态加载也就是运行时加载,即可以在程序运行时由我们决定何时加载指定的模块。这样进程启动时只加载必要的模块就行,减少了内存占用,除此之外最大的优点是,可以实现在不重启程序的情况下,实现模块的重新加载。这种技术也叫做“热更新”。
先看一下函数原型和功能:
// 按指定的模式打开动态链接库文件,并返回句柄
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 <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
//动态链接库路径
#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.8060
w __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 _init
w _ITM_deregisterTMCloneTable
w _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" {
#endif
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 dv(int a, int b)
{
return (a / b);
}
#ifdef __cplusplus
}
#endif
然后重新编译运行demo即可
二、实现热更新
重新修改一下文件实现
demo.h代码如下:
#ifndef DEMO_H
#define DEMO_H
#include <stdio.h>
// #include <stdlib.h>
#include <dlfcn.h>
//动态链接库路径
#define LIB_CACULATE_PATH "./libcaculator.so"
//函数指针
typedef int (*CAC_FUNC)(int, int);
void test(int x);
#endif
demo.cc如下:
#include "demo.h"
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>
#include <thread>
#include <atomic>
void *handle;
std::thread th;
std::atomic<int> flag(0);
std::atomic<bool> 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的热更新,运行结果如下: