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 <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即可

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

 二、实现热更新

重新修改一下文件实现

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的热更新,运行结果如下:

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

 

上一篇:Windows核心编程条件变量


下一篇:mysql.user表中Host为%的含义