很多软件为了扩展方便,具备通用性,普遍都支持插件机制:主程序的裸机功能框架不变,各个具体的功能和业务以动态链接库的形式加载进来。这样做的好处是软件发布以后不用重新编译,可以直接通过插件的形式来更新功能,实现软件升值。
插件的本质其实就是共享动态库,只不过组装的形式比较复杂。主程序框架引用的外部模块符号,运行时以动态链接库的形式加载进来并进行重定位,就可以直接调用了。我们只需要将这些功能模块实现,做成支持动态加载的插件,就可以很方便地扩展程序的功能。Linux 提供了专门的系统调用接口,支持显式加载和引用动态链接库。
- 加载动态链接库
void *dlopen(const char *filename, int flag);
void *Handle = dlopen("./libtest.so", RTLD_LAZY);
dlopen()
函数返回的是一个 void* 类型的操作句柄,通过这个句柄就可以操作显式加载到内存中的动态库。函数的第一个参数是要打开的动态链接库,第二个参数是打开的标志位:
- RTLD_LAZY: 解析动态库遇到未定义符号不退出,仍继续使用。
- RTLD_NOW: 遇到未定义符号,立即退出。
- RTLD_GLOBAL: 允许导出符号,在后面其他动态库中可以引用。
- 获取动态对象的地址
void *dlsym(void *handle, char *symbol);
void (* funcp)(int, int);
funcp = (void(*)(int, int)) dlsym(Handle, "myfunc");
dlsym() 函数根据动态链接库句柄和要引用的符号,返回符号对应的地址。通过这个指针,我们就可以引用动态库里的这个函数或全局变量了。
- 关闭动态链接库
int dlclose(void *Handle);
该函数会将加载到内存的共享库的引用计数减一,当引用计数为 0 时,该动态共享库便会从系统中卸载。
- 动态库错误函数
const char *dlerror(void);
当动态链接库操作函数失败时,dlerror 将返回出错信息。若没有出错,则 dlerror 返回 NULL。
// sub.c
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
// main.c
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
typedef int (*cac_func)(int, int);
int main(void)
{
void *handle;
cac_func fp = NULL;
handle = dlopen("./libtest.so", RTLD_LAZY);
if(!handle)
{
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
fp = dlsym(handle, "add");
if(fp)
{
printf("add:%d\n", fp(8, 2));
}
fp = dlsym(handle, "sub");
if(fp)
{
printf("sub:%d\n", fp(8, 2));
}
dlclose(handle);
exit(EXIT_SUCCESS);
}
gcc sub.c -fPIC -shared -o libtest.so
gcc main.c -ldl # 如果你的程序中使用dlopen、dlsym、dlclose、dlerror 显示加载动态库,需要设置链接选项 -ldl