在开发中,经常有这样的场景,把一些功能打包为一个动态库,这个库作为工具人,可以被其他不同进程链接,然后把自己的功能提供给对应链接的目标程序。这也是动态库最常见的用法,如下所示:
// test_lib.cc
#include <stdio.h>
int do_something_in_libso(int num)
{
printf("do_something_in_libso, num %d\n", num);
return num;
}
// g++ -fPIC --shared -o test_lib.so test_lib.cc
// main.cc
#include <stdio.h>
int do_something_in_libso(int num);
int main()
{
do_something_in_libso(100);
return 0;
}
// g++ -o main main.cc ./test_lib.so
./main
do_something_in_libso, num 100
当然,动态库的调用还有另外一种操作,比如在运行时,使用dlopen去调用动态库里面的符号。比如,如下所示:
// main2.cc
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
typedef int (*pfunc)(int);
int main(int argc, char *agv[])
{
// 打开动态库
void *handle = dlopen("./test_lib.so", RTLD_NOW);
if (handle)
{
// 查找动态库中的函数
pfunc func = (pfunc) dlsym(handle, "_Z21do_something_in_libsoi"); // 不同的g++生成的符号也许有所不同,使用nm命令可以查看
if (func)
{
int ret = func(100);
printf("ret = %d \n", ret);
}
else
{
printf("dlsym failed !!! \n");
}
dlclose(handle);
}
else
{
printf("dlopen failed! \n");
}
return 0;
}
// g++ -ldl -rdynamic -o main2 main2.cc
nm test_lib.so | grep do_something_in_libso
0000000000001119 T _Z21do_something_in_libsoi
铺垫完毕,进入主题。如果在动态库中调用一个外部的符号,会怎么样呢? 这里就有一个潜在的要求,调用动态库的进程必须要实现这个符号,如果没实现的话,就会出现找不到符号报错。(如果是通过dlopen的方式去调用动态库也是一样的,如果没有实现这个符号,会提示dlopen failed!
)
// main.cc
#include <stdio.h>
int do_something_in_libso(int num);
// 实现这个符号的定义
int do_something_in_main(int num)
{
printf("do_something_in_main, num %d\n", num);
return num;
}
int main()
{
do_something_in_libso(100);
return 0;
}
// g++ -ldl -rdynamic -o main main.cc ./test_lib.so
但是,在一些场景中,开发动态库的时候,并没有办法去控制这个动态库被使用在什么地方,什么场合,以及那些进程中。如果有些调用的进程忘记实现这个符号呢?或者对方根本不知道还需要使用前 需要实现一个这种的符号。那么,一种比较好的方式是,我们需要的是动态库里面提供一个默认的实现(或者是空的实现),如果链接动态库的外部进程实现了这个函数,那么会覆盖它。如果没有实现,那么调用默认的。对于,这种场景要怎么实现呢?
有几种办法可以解决这个问题:
- 思路一:在动态库里面提供一个函数指针,函数指针指向默认的任务函数。外部可以显示的调用它,去修改指针,指向不同的任务函数。这也是一个sdk提供服务的常见套路,提供默认的实现,但是也允许sdk的使用方自定义去注册一些功能。
typedef int (*PFUNC)(int);
int do_something_in_main_default(int num)
{
printf("do_something_in_main_default, num %d\n", num);
return 0;
}
// 函数指针指向默认的函数
PFUNC p_default = do_something_in_main_default;
// 同时提供了修改函数指针的接口
int set_do_somethine_in_main(PFUNC p)
{
p_default = p;
return 0;
}
int do_something_in_libso(int num)
{
printf("do_something_in_libso, num %d\n", num);
// 调用外部符号
if (p_default)
{
p_default(num); // 根据函数指针,访问对应的函数
}
return num;
}
- 思路二:利用弱符号的思路
__attribute__((weak))
,在动态库中定义弱符号,当在外部遇到了强符号,那么编译器会自动用强符号覆盖弱符号。那么就自动替换了。当在外部没有遇到其他强符号,那么编译器就认为弱符号是它唯一的定义。
#include <stdio.h>
__attribute__((weak)) int do_something_in_main(int num)
{
printf("do_something_in_main_default, num %d\n", num);
return 0;
}
int do_something_in_libso(int num)
{
printf("do_something_in_libso, num %d\n", num);
// 调用外部符号
do_something_in_main(num);
return num;
}
这里实现main.cc,在main中如果不定义int do_something_in_main(int num)
符号,那么会自动用动态库中的
__attribute__((weak)) int do_something_in_main(int num)
。
#include <stdio.h>
int do_something_in_libso(int num);
int main()
{
do_something_in_libso(100);
return 0;
}
// g++ -ldl -rdynamic -o main main.cc ./test_lib.so
./main
do_something_in_libso, num 100
do_something_in_main_default, num 100
如果main中定义了,那么会自动调用main中的实现。程序实现的非常简洁,对使用方也没有什么负担。
#include <stdio.h>
int do_something_in_libso(int num);
int do_something_in_main(int num) // 这里定义了自己的实现,会自动覆盖动态库中的实现
{
printf("do_something_in_main, num %d\n", num);
return num;
}
int main()
{
do_something_in_libso(100);
return 0;
}
// g++ -ldl -rdynamic -o main main.cc ./test_lib.so
小结:
本文介绍了cpp开发中动态库中调用外部符号的一些技巧。