主程序调用动态库有两种方式,即隐式调用和显式调用。
隐式调用就是共享方式,程序一开始运行就调进去。在链接时候用如下的方式链接动态库:gcc -o main main.o -L./lib -ltest(就像链接像静态库的一样)
显示调用就是在程序中用系统调用把动态库加载进来,用系统调用:dlopen、dlsym、dlerror、dlclose函数,那样在编译链接时候,就不用加上:-L./lib -ltest了。不过因为要使用dl*系列函数,需要在编译链接时要加上 -ldl 。
动态库可以直接调用主程序中定义的函数,其实现机制是:动态链接器在完成基本自举后, 动态链接器将可执行文件和链接器本身的符号表都合并到一个符号表中, 我们可以称它为全局符号表(Global Symbol Table)…..当一个新的share object被装载进来的时侯, 它的符号表会被合并到全局符号表中。因此,动态库加载后其调用的主程序中的函数就是从全局符号表中找到该函数的地址并调用的。
下面通过实例进行说明。
1、隐式调用动态库
//hi.c文件,编译为libhi.so
extern void hi_out();
void hello() {
hi_out();
}
gcc -shared -fPIC -o libhi.so hi.c
//main.c文件,编译为main
#include <stdio.h>
void hi_out() {
printf("hi out.\n");
}
extern void hello(); //语句1
int main() {
hello(); //语句2
return 0;
}
gcc main.c -L. -lhi -Wl,-rpath=. -o main
隐式调用方式,主函数可以直接调用动态库的函数,反之动态库也可以直接调用主程序中的函数。
需要注意的是,我们知道,动态库是在程序运行时链接动态库的,在运行主程序时,需要主程序能顺利找到动态库。在编译时加入-Wl,-rpath=. 这种方式是在编译时就指定了程序需要去找的路径,此外还有其他的方式,可以参考https://blog.csdn.net/qq_28584889/article/details/120070042
2、显式调用动态库
hi.c文件保持不变,同样的方式编译为libhi.so
修改一下main.c如下:
#include <dlfcn.h>
#include <stdio.h>
void hi_out() { printf("hi out.\n"); }
// extern void hello(); //语句1
int main() {
const char* pstr = "./libhi.so";
void* library = dlopen(pstr, RTLD_NOW); //如果用RTLD_LAZY,则会在语句4报错
if (!library) {
printf("load dlopen(%s): %s\n", pstr, dlerror()); //语句3,RTLD_NOW模式在这报错
return -1;
}
void (*pfun)();
pfun = (void (*)())dlsym(library, "hello");
if (pfun) {
pfun(); //语句4
}
return 0;
}
gcc main.c -ldl -o main
编译之后执行./main, 发现报错:
load dlopen(/data1/home/menglonglu/hold_proj/test/libhi.so): /data1/home/menglonglu/hold_proj/test/libhi.so: undefined symbol: hi_out
原因是: ld在生成可执行文件时, 默认只导出被其他动态库使用的符号. 因为是使用dlopen去动态加载libhi.so, 那么链接时ld并不知道可执行文件中的hi_out会被外部引用, 也就不会导出hi_out到动态符号表去. 当dlopen打开libhi.so时, 动态链接器在全局符号表中找不到hi_out符号, 理所当然就报错了。
要解决这个问题只要给链接器加上参数-E将主程序中所有全局符号放到动态符号表中即可, 由于生成可执行文件一般都是gcc直接生成, 因此可以使用gcc -Wl,-E来将-E参数传给ld来完成创建一个可以被动态链接的可执行文件。
编译主程序: gcc main.c -ldl -Wl,-E -o main 编译成可执行文件,运行ok。
或者把-Wl,-E 换成 -rdynamic,都可以实现同样的效果。
-rdynamic 是一个 连接选项 ,它将指示连接器把所有符号(而不仅仅只是程序已使用到的外部符号)都添加到动态符号表(即.dynsym表)里,以便那些通过 dlopen() 或 backtrace() (这一系列函数使用.dynsym表内符号)这样的函数使用。具体可参考https://blog.csdn.net/adam040606/article/details/53097650
3、拓展:使用cmake编译
我发现当使用cmake编译例2中的主程序时,应当是默认把所有符号都加载到全局符号表里了。
我们不加任何选项的,创建一个 CMakeList.txt并使用cmake编译。
cmake_minimum_required(VERSION VERSION 2.8.12)
project(test)
add_executable(main main.c)
target_link_libraries(main -ldl)
然后cmake . 生成Makefile,make编译成main执行文件,运行ok。
参考链接:https://blog.csdn.net/mw_nice/article/details/108253409