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

一、背景介绍

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

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

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

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

二、程序测试

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容