话说前一篇文章一文教你如何构建静态链接库向大家介绍了如何构建静态库,虽然解决了我们的一大麻烦,但静态库在使用中任然存在一些缺点。
- 软件更新麻烦:假如你对之前库里的某个函数功能有了更好的实现方法,想在某个之前已经使用了该函数的项目中使用新版本的库函数,这时你不得不重新编译一遍。如果项目规模小还可以,但如果项目规模很大,编译起来需要几个小时呢?
- 几乎你的每个程序都大量使用了标准I/O函数(printf,scanf等),如果采用静态链接,那么程序在运行时,这些函数的代码会被复制到每个运行进程的代码段中去。试想,在一个运行了成千上百个进程的系统上,这是对内存资源多大的一个浪费啊!
动态链接库又叫共享库(shared object)就可以解决以上问题。它其实是一个目标模块,在运行或加载时,可以加载到任意的内存地址,并和正在运行在内存中的程序链接起来。它是由一个叫做动态链接器的程序来执行的。在linux系统中,用.so后缀来表示共享库,在windows下用DLL来表示。
- 在任何给定的文件系统中,对于一个库只有一个 .so文件,所有可执行目标文件共享这个库中的代码和数据。
- 在内存中,一个共享库的,text段可以被不同的正在运行的进程共享。
说了这么多,那到底怎么建立和使用自己的共享库呢?
需求:将c文件addvec.c和mulvec.c编进一个名叫libvector.so的共享库。并在之后的main2.c文件中调用该库中的addvec()函数。
1. 创建共享库
- -share:指示编译器生成一个共享的目标文件
- -fpic:指示编译器生成与位置无关的代码
2. 使用共享库
此时,在创建可执行文件prog2l时,并没有将共享库中的代码和数据复制进去,而仅仅是复制了一些重定位信息和符号表信息。只有等到加载器execve加载可执行目标文件时,这时动态链接器才会依次将libc.so以及libvector.so的代码和数据复制到内存段中供prog2l进程调用。
看图☞
3. 从应用程序中加载和链接共享库
前面我们已经探讨了应用程序在加载后运行前,动态链接器加载和链接动态库的过程。然而,应用程序还可以在它运行时调用动态加载器来加载和链接某个共享库,而无需在编译时链接。
linux系统为动态链接器提供一个简单的接口:
- dlopen()
libname:需要加载和链接的动态库路径名;
flag:指示链接器解析外部符号的时间点(必须选则立即或推迟中的一个),RTLD_NOW(加载时立即解析)或RTLD_LAZY(推迟到执行时解析);RTLD_GLOBAL(用其它已用RTLD_GLOBAL参数打开了的库解析当前库的外部符号),可以与之前两个标志中的一个取或。
- dlsym()
handle:一个dlopen函数返回的库的句柄的指针
symbol:库中某个符号(函数名或变量名)
- dlclose()
如果没有其它共享库还在使用这个库,就卸载handle指向的共享库
- dlerror()
下面展示一段dll.c文件,它可以在运行时利用该接口动态链接共享库libvector.so,并调用其中的addvec函数。
如果要编译这个程序,我们可以利用下面的方式调用GCC: