### **工程问题**

提出这么一个问题:A公司为B公司的一条自动化流水线开发一个检测装置,B公司要求可以检测流水线最终的产品是否合格。具体是:
- 第一版需要检测产品的涂层颜色是否均匀、外观是否有破损两个指标。
- 系统上线运行后,支持B公司可以自主增加新的检测项目。

这个工程需求,简单地讲就是需要A公司开发的检测系统,能自动链接目前尚未出现的、未来的接口,这就需要A公司不是开发出检测外观、涂层颜色等具体功能的软件,而是要给B公司提供一个具备可拓展的软件“框架”,使得B公司后续可以按照自己的实际需求来拓展检测装置的功能。

<br>

### **动态加载库**

动态库最大的优点,是将链接推迟到运行时,由于运行时才链接动态库,这就给链接的目标留下了选择的空间。结合以上工程需求,可以让程序在运行的时候,为其指定要链接的动态库,以达到可以按需链接动态库的目的,这种做法称为动态库的动态加载。

具体做法如下:
- 约定好函数接口,比如 void detection()
- 将各个不同需求的实现代码封装到不同的库中,比如libcolor.so、libshape.so
- 编写相应配置文件,指定程序在启动后要链接的具体的库

<br>

### **具体实现**
1. 接口实现:
```C
// a.c
void detection()
{
    printf("正在检测颜色是否均匀...\n");
}

// b.c
void detection()
{
    printf("正在检测外观是否破损...\n");
}
```

2. 将不同的功能模块制作成动态库:
```bash
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
```

3. 编写一个配置文件,指定程序需要加载的动态库:
```bash
gec@ubuntu:~$ cat config
libcolor.so
```

4. 读取配置文件,并根据指示加载指定的动态库:
```C
#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();
}
```

<br>

### **代码解析**

打开和关闭动态库,获取动态库的操作句柄:  
![正文-dlopen与dlclose.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0ab946982247463ba66a66bd9b138d87~tplv-k3u1fbpfcp-watermark.image)

关键点:
- RTLD_LAZY意味着打开动态库时,并不立即解析库中的函数符号的内存位置,而是等待程序实际调用时才临时去解析。
- RTLD_NOW与上述含义相反,它意味着打开动态库时就立即解析库中的函数符号的内存位置。
- 不管是LAZY还是NOW,库中的静态数据符号都将被立即解析。
![正文-dlsym.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/29ab3631885b4694899d675f478c7d8e~tplv-k3u1fbpfcp-watermark.image)

关键点:
- 该函数用于在动态库中获取指定的函数入口地址。


## 选自:粤嵌-嵌入式课堂笔记

## 联系人:18028569040(曾小美老师·微信)