一、背景介绍
在程序开发过程中,动态库可以需要调用其它外部模块中的函数,以便进行数据交互,减少代码重复等功能
本章解决的问题是:动态库在执行过程中如何到其它外部模块里调用函数?
在调用过程中需要考虑一个问题,外部模块的函数在哪里呢?怎么才能找到它藏在内存的哪个地址?
解决办法:其它外部模块向动态库注册回调函数!
二、程序测试
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_main
和sub_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);
然后通过dlopen
、dlsym
等函数加载动态库,并通过register_func
函数向动态库注册自己的回调函数func_in_main
,然后利用func_in_lib
来处理自己的回调函数。
参考:
干货 | 在动态库中如何调用外部函数?
https://www.eet-china.com/mp/a51759.html