本节课我们来使用四个文件: file1.c、file1.h、file2.c、file2.h 在 Linux 操作系统平台上 使用 GCC 编译器制作成动态库 libmylib.so。
然后将主程序 main.c 编译成可执行文件 myapp,再通过动态加载的方式运行此可执行程序。
GCC 生成动态库步骤:
-fPIC 选项)。libmylib.so生成目标文件
weimingze@mzstudio:~$ lsfile1.c file1.h file2.c file2.hweimingze@mzstudio:~$ gcc -fPIC -c file1.c file2.cweimingze@mzstudio:~$ lsfile1.c file1.h file1.o file2.c file2.h file2.oweimingze@mzstudio:~$ gcc -shared -o libmylib.so file1.o file2.oweimingze@mzstudio:~$ lsfile1.c file1.h file1.o file2.c file2.h file2.o libmylib.so使用于位置无关的目标文件生成动态库 libmylib.so。
gcc -shared -o libmylib.so file1.o file2.ogcc 选项说明
-fPIC-shared这样我们即生成了动态库文件 libmylib.so,合作方需要将此文件连同 两个头文件 file1.h、file2.h 一起提供给 项目发布方才能够编译成为可执行文件。
下面我们再来讲解一下如何使用动态链接库。
主程序要使用动态链接库有两种方法:
1、动态库的静态链接时加载
我们先来讲述静态链接时加载的主程序的编译和运行过程,假设动态的库文件 libmylib.so 和头文件 file1.h和 file2.h 都存在于 mylib2 文件夹下,结构如下所示
weimingze@mzstudio:~$ tree ..├──main.c└──mylib2├──file1.h├──file2.h└──libmylib.so编译和运行过程如下:
weimingze@mzstudio:~$ gcc -c main.c -I mylib2weimingze@mzstudio:~$ gcc -o myapp main.o -L mylib2 -l mylibweimingze@mzstudio:~$ ./myapp./myapp: error while loading shared libraries: libmylib.so: cannot open shared object file: No such file or directory从上述运行结果可知,主程序 myapp 运行时出错,这是因为在程序运行时需要找到 libmylib.so 所在的文件夹。也就是说主程序 myapp 启动之前,需要使用 Shell 的环境变量 LD_LIBRARY_PATH 来设置动态库的路径,方法如下:
weimingze@mzstudio:~$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./mylib2weimingze@mzstudio:~$ ./myapp库函数 myfunc1 被调用库函数 myfunc2 被调用可见在启动应用前,设置了 LD_LIBRARY_PATH 的路径为 ./mylib2,主程序能够正常运行了。
2、动态库的运行时动态加载
下面我们在来说一下如何使用 dl 系列的 API(应用程序接口)来 真正的动态加载和使用 libmylib.so 中的函数。
动态加载的主要函数
void *dlopen(const char *filename, int flags); | filename 加载动态库并返回操作句柄,引用计数加1,如果动态库已经加载则只是将引用计数加1。成功返回非空值,失败返回 NULL。 |
int dlclose(void *handle); | |
void *dlsym(void *handle, const char *symbol); | symbol参数指定名字的符号(通常是函数名),并返回符号对应的函数地址。失败返回 NULL |
char *dlerror(void); |
修改 main.c 文件如下:
// filename: main.c#include <stdio.h>#include <dlfcn.h>int main() { void*handle;// 保存动态库的打开句柄 void(*fn1)(void);// 用于指向动态库内的函数 myfunc1 void(*fn2)(void);// 用于指向动态库内的函数 myfunc2 // 打开动态库 handle = dlopen("./mylib2/libmylib.so", RTLD_LAZY); if (NULL == handle) { fprintf(stderr, "%s\n", dlerror()); return 1; } // 获取函数地址 fn1 = dlsym(handle, "myfunc1"); if (NULL == fn1) { printf("动态库内没有找到 myfunc1函数"); goto exit_main; } fn2 = dlsym(handle, "myfunc2"); if (NULL == fn2) { printf("动态库内没有找到 myfunc2函数"); goto exit_main; } // 4. 使用函数指针调用动态库中的函数 fn1(); fn2();exit_main: // 5. 关闭动态库 dlclose(handle); return 0;}程序结构如下:
weimingze@mzstudio:~$ tree ..├──main.c└──mylib2└──libmylib.so编译和运行结果如下:
weimingze@mzstudio:~$ gcc -o myapp main.cweimingze@mzstudio:~$ ./myapp库函数 myfunc1 被调用库函数 myfunc2 被调用可见,使用动态加载时,编译的时候都不依赖库的头文件,使用 dlopen 打开库文件,在使用 dlsym 定位到库函数地址,然后就可以使用函数指针来调用动态库中的函数了。
静态库和动态库对比
动态库是现代软件开发的更常见选择,特别是在大型系统中,因为它支持模块化更新和更高效的资源利用。
实验:
尝试在自己的编译环境下制作动态库文件。