在动态库中如何调用外部函数(注册回调函数)

一、背景介绍

在程序开发过程中,动态库可以需要调用其它外部模块中的函数,以便进行数据交互,减少代码重复等功能

本章解决的问题是:动态库在执行过程中如何到其它外部模块里调用函数?

在调用过程中需要考虑一个问题,外部模块的函数在哪里呢?怎么才能找到它藏在内存的哪个地址?

解决办法:其它外部模块向动态库注册回调函数!

二、程序测试

2.1 动态库

2.1.1 源码

// g++ lib.cpp -fPIC -shared -o lib.so -lrt -lpthread
#include <stdio.h>
#include <thread>
#include <chrono>

typedef void (*pf_void)(void);

// 默认实现,必须有,否则线程创建会报错
extern "C" void func_in_main_def(void){
    printf("the main is lazy, do NOT register me! \n");
}

// 定义外部函数指针,需先定义函数再定义指针
pf_void func_in_main = func_in_main_def;

extern "C" void mytimer(void){
    int cnt =0;
    while (1)
    {
        printf("lib is runing %d !\n",cnt++);
        func_in_main();
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    }
}

// 需先定义mytimer函数,再创建mytimer线程
std::thread sub_thread(mytimer);

extern "C" void register_func(pf_void pf){
    func_in_main = pf;
}

extern "C" int func_in_lib(int k){
    printf("func_in_lib is called \n");

    if (func_in_main)
        func_in_main();

    return k + 1;
}

2.1.2 编译

编译:
g++ lib.cpp -fPIC -shared -o lib.so -lrt -lpthread

2.2 外部模块

2.2.1 源码

// g++ registermain.cpp -ldl -o registermain
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <chrono>
#include <thread>

typedef void (*pf_void)(void);
typedef int (*pfunc)(int);
typedef int (*pregister)(pf_void);

void func_in_main(void){
    printf("func_in_main \n");
}

int main(int argc, char *agv[]){
    int a = 1;
    int b;
    // 打开动态库
    printf("press <enter> key to pass! \n");
    void *handle = dlopen("./lib.so", RTLD_NOW);
    // char ch =getchar(); // 测试动态库中子线程运行时序,在加载动态库即开始运行子线程
    if (handle){
        // 查找动态库中的注册函数
        pregister register_func = (pregister) dlsym(handle, "register_func");
        if (register_func){
            register_func(func_in_main);
        }
        // 查找动态库中的函数
        pfunc func = (pfunc) dlsym(handle, "func_in_lib");
        if (func){
            b = func(a);
            printf("b = %d \n", b);           
        }
        else{
            printf("dlsym failed! \n");
        }
    }
    else{
        printf("dlopen failed! \n");
    }
    while (1){
        printf("main is runing! \n");
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    }
    
    dlclose(handle);
    return 0;
}

2.2.2 编译与运行

编译:
g++ registermain.cpp -ldl -o registermain

运行:
./registermain

输出:

func_in_lib is called 
func_in_main 
b = 2 
main is runing! 
lib is runing 0 !
func_in_main 
main is runing! 
lib is runing 1 !

三、代码解释

3.1 流程图

名词解释:

  • 注册函数:注册函数register_func()在动态库中定义,在外部模块中使用。
  • 回调函数:被动态库调用的外部模块中的函数,此处是func_in_main
  • 注册回调函数:一种行为,程序通过dlopen加载动态库,将自己的函数向动态库注册,让动态库可以调用自己的函数;有时也指注册函数。

整个程序运行过程中,外部模块与动态库的交互流程如下图:

其中粉色为外部模块执行代码,绿色为动态库执行代码,红色代表线程,该程序运行时外部模块执行主线程,在加载动态库后,动态库创建子线程。

注意:

  • 在实际测试中,通过dlopen加载动态库后,紧接着通过getchar()函数阻塞主线程,会发现终端打印the main is lazy, do NOT register me!,因此得出结论:在dlopen()加载动态库时,初始化全局变量,动态库的子线程就开始运行了。

  • 若未阻塞主线程并且主线程成功注册回调函数后,子线程中的函数指针func_in_main未执行动态库中的默认回调函数func_in_main_def,而是执行主线程中的func_in_main函数。(func_in_main在子线程为函数指针变量,在主线程中为函数名称)

博主仍有一个困惑,主线程在加载动态库之后与执行注册回调函数前(中间不阻塞),子线程有没有可能调用的是动态库中的默认回调函数func_in_main_def。(博主将子线程延迟改为1ms,仍然是执行的主线程的回调函数!本质是探究关于子线程启动顺序的问题。)

3.2 动态库

动态库中func_in_mainsub_thread都是全局变量,前者用来保存外部模块中的回调函数,以便在动态库的不同地方使用,后者是子线程。

该动态库的目的是创建一个线程,定期调用外部(主程序)注册的函数。默认情况下,线程将调用func_in_main_def,但主程序可以通过调用register_func来注册自己的函数。func_in_lib函数也可以被主程序调用,以便在动态库内部执行注册的外部函数。

动态库被调用时会创建一个子线程,该子线程每隔1000ms循环执行。若外部函数注册回调函数后,该子线程就会将默认的函数替换为外部的回调函数进行执行。

外部函数也可以通过特定的函数,手动调用动态库中得函数func_in_lib进行回调函数处理。

3.3 外部模块

外部模块通过typedef定义了多种类型函数指针:

typedef void (*pf_void)(void);
typedef int (*pfunc)(int);
typedef int (*pregister)(pf_void);

然后通过dlopendlsym等函数加载动态库,并通过register_func函数向动态库注册自己的回调函数func_in_main,然后利用func_in_lib来处理自己的回调函数。

参考:
干货 | 在动态库中如何调用外部函数?
https://www.eet-china.com/mp/a51759.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容