盒子
盒子
文章目录
  1. 函数签名
    1. C函数签名
    2. C++函数签名
      1. 将C++函数签名转换为C函数签名
  2. 加载动态库

利用dlopen,dlsym加载动态链接库

最近碰到一个需求,程序加载的动态库中有若干个函数,如何根据函数签名从这些函数中返回唯一匹配的函数。

分析该问题,首先需要知道编译后的目标文件中,每个函数的函数签名是什么。其次,要如何根据函数签名从动态库中获得对应的函数。

函数签名

C函数签名

C函数中,不能通过改变函数的参数列表实现重载,因为只使用函数名作为签名。

example:

1
2
3
4
5
6
7
8
9
10
11
12
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 calc.c -o libcalc.so

使用nm查看libcalc.so的函数列表, 可以看到四个对应的函数

1
2
3
4
5
nm libcalc.so
0000000000000eb0 T _add
0000000000000ef5 T _div
0000000000000ee2 T _mul
0000000000000ed0 T _sub

C++函数签名

c++函数以函数名加参数类型作为签名,所以可以通过改变参数列表实现重载。

将上述文件保存为cpp之后,用g++编译成动态链接库,并查看函数列表

g++ -fPIC -shared calc.cpp -o libcalc.so

1
2
3
4
0000000000000ebc T __Z3addii
0000000000000ef5 T __Z3divii
0000000000000ee2 T __Z3mulii
0000000000000ed0 T __Z3subii

以加法函数为例,add为函数名,ii表示两个参数分别为int,int

如果重载add函数,如下:

1
2
3
int add(float a, int b) {
return (a + b);
}

编译之后的函数签名如下,add的第一个参数为float,第二个为int

0000000000000eb0 T __Z3addfi

将C++函数签名转换为C函数签名

在某些场景下,需要用c来调c++函数,就需要对c++函数做包装,使得函数名长得和C函数名一致。通过extern “C”来实现:

1
2
3
extern "C" {
code
}

对应的code函数名就与C函数名一致

加载动态库

知道了函数的签名,可以通过dlopen, dlsym在动态库中查找对应的函数。

  • dlopen:打开指定动态库, 返回handle句柄给调用进程
    • 打开方式有多种,比如
      • RTLD_LAZY: 在dlopen返回前,对于动态库中未定义的符号不执行解析
      • RTLD_NOW: 需要在dlopen返回前,解析出所有未定义符号,否则返回错误
  • dlerror: 返回出现的错误
  • dlsym: 通过句柄和连接符名称获取函数,并返回该函数指针
  • dlclose: 关闭打开的库
1
2
3
4
5
#include <dlfcn.h>
void *dlopen(const char *filename, int mode);
char *dlerror(void);
void *dlsym(void *handle, const char *symbol);
int dlclose(void *handle);

采用上述生成的libcalc.so, 测试dlsym等函数功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <iostream>
#include <functional>
//动态链接库路径
#define LIB_CALCULATE_PATH "./libcalc.so"
//函数指针
typedef int (*CAC_FUNC)(int, int);
typedef std::function<int(int, int)> FUNC;
int main() {
void *handle;
char *error;
FUNC func = NULL;
//打开动态链接库
handle = dlopen(LIB_CALCULATE_PATH, RTLD_LAZY);
if (!handle) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
//清除之前存在的错误
dlerror();
//获取一个函数
// 将dlsym返回的 void* 转化为CAC_FUNC的函数指针,然后赋给func
func = (CAC_FUNC)(dlsym(handle, "add"));
if ((error = dlerror()) != NULL) {
fprintf(stderr, "%s\n", error);
exit(EXIT_FAILURE);
}
printf("add: %d\n", func(2,7));
func = (CAC_FUNC)(dlsym(handle, "sub"));
printf("sub: %d\n", func(9,2));
func = (CAC_FUNC)dlsym(handle, "mul");
printf("mul: %d\n", func(3,2));
func = (CAC_FUNC)dlsym(handle, "div");
//*(void **) (&cac_func) = dlsym(handle, "div");
printf("div: %d\n", func(8,2));
//关闭动态链接库
dlclose(handle);
exit(EXIT_SUCCESS);
}

最后编译,运行如下

1
2
3
4
5
6
➜ tools git:(master) ✗ g++ -rdynamic -o main main.cpp -ldl
➜ tools git:(master) ✗ ./main
add: 2
sub: 7
mul: 6
div: 4

利用上述的方案,能够方便的使用动态库中的函数,提高灵活性。

参考资料

http://www.cnblogs.com/Anker/p/3746802.html