Linux下动态链接库的2种链接方式
方式一
通过 dlopen, dlsym, dlerror, dlclose 在代码中直接打开与使用动态链接库
- dlopen 用于打开动态链接库,返回句柄
- dlsym 使用dlopen返回的句柄与函数名来获得函数位置,返回函数指针
- dlclose 关闭动态链接库
- dlerror 当动态链接库函数操作失败时,返回出错信息;NULL表示成功
看以下示例程序
// fibo.cpp
#include <iostream>
#include <vector>
using namespace std;
// Unmangling the function name for dlsym convenience, as this is C++ file
extern "C" {
unsigned long fibo(int n);
}
unsigned long fibo(int n)
{
unsigned long vec[94] = {0, 1, 1};
long result = 0;
if (n <= 0 || n > 93) { // know why choose 93? Haha
result = 0;
}
else { // 1 <= n <= 93
for (int i=3; i<=n; i++) {
vec[i] = vec[i-1] + vec[i-2];
}
result = vec[n];
}
return result;
}
该程序需使用如下命令编译成动态链接库
g++ -fPIC -shared fibo.cpp -o libfibo.so
在程序中,之所以使用 extern “C” 来声明 fibo 函数,是因为这个源代码文件是一个C++文件,默认编译出来的so文件中的fibo函数的名字是会被 mangling 的。这就会引起使用者在调用 dlsym 函数获取 fibo 函数地址的时候,需要填写一个mangling的名称,而不是直接写 “fibo”, 从而造成不便。为了使用者的方便,这里用 extern “C” 来声明 fibo,以去除名称的mangling.
另外,我们可以使用以下2个命令中的任何一个来验证fibo函数的命令在库中已经被unmangling了。
objdump -t libfibo.so | grep fibo
nm libfibo.so | grep fibo
以下是调用者程序:
// use_libfibo_way1.cpp
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <iostream>
using namespace std;
#define LIB_FIBO_PATH "./libfibo.so"
typedef unsigned long (*FUNC_PTR)(int);
int main()
{
char * error;
void * handler = dlopen(LIB_FIBO_PATH, RTLD_LAZY);
if (!handler) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
FUNC_PTR fibo = (FUNC_PTR)(dlsym(handler, "fibo"));
if ((error = dlerror()) != NULL) {
fprintf(stderr, "%s\n", error);
exit(EXIT_FAILURE);
}
cout << "fibo[3] = " << fibo(3) << endl;
cout << "fibo[5] = " << fibo(5) << endl;
cout << "fibo[93] = " << fibo(93) << endl;
dlclose(handler);
return 0;
}
该程序需使用如下命令编译:
g++ -rdynamic use_libfibo_way1.cpp -ldl -o a1.out
运行时,直接运行 a1.out 即可。
可以看出,这种调用动态链接的方式的缺点是:
- 需要在程序中或配置文件里注明库文件的位置
- 需使用额外的库函数 dlopen, dlsym, dlerror, dlclose 来辅助工作
优点是:
可以不用include头文件
方式二
编译时使用链接选项指定动态链接库
调用者程序如下:
// use_libfibo_way2.cpp
#include <iostream>
using namespace std;
extern "C" {
unsigned long fibo(int n);
}
int main()
{
cout << "fibo[3] = " << fibo(3) << endl;
cout << "fibo[5] = " << fibo(5) << endl;
cout << "fibo[93] = " << fibo(93) << endl;
return 0;
}
该程序使用如下命令进行编译:
g++ use_libfibo_way2.cpp -L. -lfibo -o a2.out
而运行 a2.out 时,假如 libfibo.so 并不在系统搜索路径中,即不在环境变量 LD_LIBRARY_PATH 中,则需加入,然后运行:
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
./a2.out
如果不想运行依赖于环境变量,可以在编译的时候使用这个命令:
g++ way2.cpp -lfibo -o a2.out -L. -Wl,-rpath=.
这里的 “-rpath=.” 就表示把”当前路径作为搜索路径“传递给了链接器。于是直接运行 ./a2.out 而不用改变环境变量 LD_LIBRARY_PATH 了。
这种方式的优点是:
程序简洁,不用依赖于其他库,即不用依赖 dlopen, dlsym, dlerror, dlclose 所在的 libdl.so 库
缺点是:
一般都需要include头文件(虽然本例中没有include头文件,那只是因为程序简单)
其他
方式二是常用的方式,但是方式一也需要知道。为何呢?
除了因为方式一可能有其特定的使用场合之外,还有一个原因:
如果使用 ldd 命令来查看一个二进制文件都链接了哪些动态链接库,你会发现方式二的 a2.out 的输出中有 libfibo.so , 而方式一的 a1.out 的输出中没有 libfibo.so!
这是意料之中的,因为a1.out在其编译选项中本就没有用到 libfibo.so, 不过它用到了 libdl.so , 因此可以看到这个库。
这就意味着 ldd 命令无法看出方式一的二进制文件究竟链接了哪些动态链接库,唯有看源码才能看到。
ldd的输出如下:
$ldd a1.out
linux-vdso.so.1 (0x00007ffc4a3fd000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007fc1f87b8000)
libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00007fc1f842f000)
libc.so.6 => /lib64/libc.so.6 (0x00007fc1f8075000)
/lib64/ld-linux-x86-64.so.2 (0x00007fc1f8bbf000)
libm.so.6 => /lib64/libm.so.6 (0x00007fc1f7d2a000)
libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fc1f7b13000)
$ldd a2.out
linux-vdso.so.1 (0x00007ffddf591000)
libfibo.so => not found >>> 因为没有运行 export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH 否则可以找到
libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00007f3266225000)
libc.so.6 => /lib64/libc.so.6 (0x00007f3265e6b000)
libm.so.6 => /lib64/libm.so.6 (0x00007f3265b20000)
/lib64/ld-linux-x86-64.so.2 (0x00007f32667b1000)
libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f3265909000)
(完)