工程问题
提出这么一个问题:A公司为B公司的一条自动化流水线开发一个检测装置,B公司要求可以检测流水线最终的产品是否合格。具体是:
- 第一版需要检测产品的涂层颜色是否均匀、外观是否有破损两个指标。
- 系统上线运行后,支持B公司可以自主增加新的检测项目。
这个工程需求,简单地讲就是需要A公司开发的检测系统,能自动链接目前尚未出现的、未来的接口,这就需要A公司不是开发出检测外观、涂层颜色等具体功能的软件,而是要给B公司提供一个具备可拓展的软件“框架”,使得B公司后续可以按照自己的实际需求来拓展检测装置的功能。
动态加载库
动态库最大的优点,是将链接推迟到运行时,由于运行时才链接动态库,这就给链接的目标留下了选择的空间。结合以上工程需求,可以让程序在运行的时候,为其指定要链接的动态库,以达到可以按需链接动态库的目的,这种做法称为动态库的动态加载。
具体做法如下:
- 约定好函数接口,比如 void detection()
- 将各个不同需求的实现代码封装到不同的库中,比如libcolor.so、libshape.so
- 编写相应配置文件,指定程序在启动后要链接的具体的库
具体实现
- 接口实现:
// a.c
void detection()
{
printf("正在检测颜色是否均匀...\n");
}
// b.c
void detection()
{
printf("正在检测外观是否破损...\n");
}
- 将不同的功能模块制作成动态库:
gec@ubuntu:~$ gcc a.c -o a.o -c -fPIC
gec@ubuntu:~$ gcc -shared -fPIC -o libcolor.so a.o
gec@ubuntu:~$
gec@ubuntu:~$ gcc b.c -o b.o -c -fPIC
gec@ubuntu:~$ gcc -shared -fPIC -o libshape.so b.o
gec@ubuntu:~$
gec@ubuntu:~$ ls
libcolor.so libshape.so
- 编写一个配置文件,指定程序需要加载的动态库:
gec@ubuntu:~$ cat config
libcolor.so
- 读取配置文件,并根据指示加载指定的动态库:
#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
int main(int argc, char **argv)
{
// 读取配置文件
FILE *fp = fopen("config", "r");
char *lib = calloc(1, 30);
fgets(lib, 30, fp);
fclose(fp);
// 根据配置文件打开指定的库
void *handle = dlopen(strtok(lib, "\n"), RTLD_NOW);
if(handle == NULL)
{
printf("加载动态库[%s]失败:%s\n", lib, strerror(errno));
exit(0);
}
// 在库中查找事先约定好的接口
void (*detect)(void);
detect = dlsym(handle, "detect");
if(detect == NULL)
{
printf("查找符号[%s]失败:%s\n", "detect", strerror(errno));
exit(0);
}
// 潇洒地调用该接口
detect();
}
代码解析
打开和关闭动态库,获取动态库的操作句柄:
关键点:
- RTLD_LAZY意味着打开动态库时,并不立即解析库中的函数符号的内存位置,而是等待程序实际调用时才临时去解析。
- RTLD_NOW与上述含义相反,它意味着打开动态库时就立即解析库中的函数符号的内存位置。
- 不管是LAZY还是NOW,库中的静态数据符号都将被立即解析。
关键点:
- 该函数用于在动态库中获取指定的函数入口地址。