动态链接库为模块化应用程序提供了一种方式,使得更新和重用程序更加方便。当几个应用程序在同一时间使用相同的函数时,它也帮助减少内存消耗,这是因为虽然每个应用程序有独立的数据拷贝,但是它们的代码是共享的。
(1)动态链接库的概念
动态链接库是应用程序的一个模块,这个模块用于导出一些函数和数据供程序中的其他模
块使用。应该从以下 3 个方面来理解这个概念:
动态链接库是应用程序的一部分,它的任何操作都是代表应用程序进行的。所以动态链接库在本质上与可执行文件没有区别,都是作为模块被进程加载到自己的空间地址的。
动态链接库在程序编译时并不会被插入到可执行文件中,在程序运行时整个库的代码才会调入内存,这就是所谓的“动态链接”。
如果有多个程序用到同一个动态链接库,Windows 在物理内存中只保留一份库的代码,仅通过分页机制将这份代码映射到不同的进程中。这样,不管有多少程序同时使用一个库,库代码实际占用的物理内存永远只有一份。
动态链接库(DynamicLink Libraries)的缩写是 DLL,大部分动态链接库镜像文件的扩展
名为 dll,但扩展名为其他的文件也有可能是动态链接库,如系统中的某些 exe文件、各种控件(*.ocx)等都是动态链接库。
(2) Window下创建动态链接库
[1] 动态链接库中的函数DLL 能够定义两种函数,导出函数和内部函数。导出函数可以被其他模块调用,也可以被定义这个函数的模块调用,而内部函数只能被定义这个函数的模块调用。
[2] *.h
#ifdef MY09DELLDEMO_EXPORTS
#define MY09DELLDEMO_API __declspec(dllexport)
#else
#define MY09DELLDEMO_API __declspec(dllimport)
#endif
// This class is exported from the 09DellDemo.dll
class MY09DELLDEMO_API CMy09DellDemo {//MY09DELLDEMO_API声明要导出声明要导出的类*****
public:
CMy09DellDemo(void);
// TODO: add your methods here.
};
extern MY09DELLDEMO_API int nMy09DellDemo;//MY09DELLDEMO_API声明要导出的变量*****
MY09DELLDEMO_API int fnMy09DellDemo(void);//MY09DELLDEMO_API声明要导出的函数
MY09DLLDEMO_API 宏是 VC++自动生成的,在这里它被解释为__declspec(dllexport),
下面的变量、函数和类将从 DLL 模块中导出。编译连接程序,最后 09DllDemo 工程产生的文件中有 3 个可以被其他工程使用:09DllDemo 文件夹下的 09DllDemo.h 头文件、debug 文件夹(或者 Release 文件夹)下的 09DllDemo.dll 文件和 09DllDemo.lib 文件。
.dll 文件就是动态链接库,.lib 文件是供程序开发用的导入库,.h 文件包含了导出函数的声明。
[3] *.cpp
// 09DellDemo.cpp : Defines the entry point for the DLL application.
//
#include "stdafx.h"
#include "09DellDemo.h"
BOOL APIENTRY DllMain//动态链接库的入口点
(
HANDLE hModule, //本Dll模块句柄
DWORD ul_reason_for_call, //调用的原因
LPVOID lpReserved//没有被使用
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH://动态链接库刚被映射到某个进程的地址空间<-->4
case DLL_THREAD_ATTACH://应用程序创建了一个新的线程<-->3
case DLL_THREAD_DETACH://应用程序某个线程正常的终止
case DLL_PROCESS_DETACH://动态链接库将被卸载
break;
}
return TRUE;
}
// This is an example of an exported variable
MY09DELLDEMO_API int nMy09DellDemo=0;//声明要导出的变量
// This is an example of an exported function.
MY09DELLDEMO_API int fnMy09DellDemo(void)//声明要导出的函数
{
return 42;
}
// This is the constructor of a class that has been exported.
// see 09DellDemo.h for the class definition
CMy09DellDemo::CMy09DellDemo()//声明要导出的类
{
return;
}
[4] 使用导出函数
调用 DLL 中的导出函数有两种方法:
(1)装载期间动态链接。模块可以像调用本地函数一样调用从其他模块导出的函数(API
函数就是这样调用的)。装载期间链接必须使用 DLL 的导入库(.lib 文件),它为系统提供了加载这个 DLL 和定位 DLL 中的导出函数所需的信息。
所谓装载期间动态链接,就是应用程序启动时由载入器(加载应用程序的组件)载入
09DllDemo.dll 文件。
这种方法加载 DLL 库的缺点很明显,如果用户丢失了 DLL文件,那么程序是永远也不能
启动了。所以很多时候要采取运行期间动态链接的方法,以便决定加载失败后如何处理。
(2)运行期间动态链接。模块也可以使用LoadLibrary或者 LoadLibraryEx 函数在运行期
间加载这个 DLL。DLL被加载之后,加载模块调用GetProcAddress函数取得 DLL 导出函数的地址,然后通过函数地址调用 DLL 中的函数。
使用第 1 种方法时,9.1.3 小节最后生成的 3 个文件都会被用到,使用第 2 种方法时,只
有 09DllDemo.dll 文件会被用到。
Eg1:装载期动态链接
#include <windows.h>
#include "09DllDemo.h"
// 指明要链接到 09DllDemo.lib 库
#pragma comment(lib,"09DllDemo")
void main()
{ // 像调用本地函数一样调用09DllDemo.dll 库的导出函数
ExportFunc("大家好!");
}
Eg2:运行期动态链接
#include <windows.h>
// 声明函数原形
typedef void (*PFNEXPORTFUNC)(LPCTSTR);
int main(int argc, char* argv[])
{ // 加载 DLL 库
HMODULE hModule = ::LoadLibrary("..\\09DllDemo\\Debug\\09DllDemo.dll");
if(hModule != NULL)
{ // 取得 ExportFunc 函数的地址
PFNEXPORTFUNC mExportFunc =(PFNEXPORTFUNC)::GetProcAddress(hModule,"ExportFunc");
if(mExportFunc != NULL)
{ mExportFunc("大家好!");}
// 卸载 DLL 库
::FreeLibrary (hModule);
}
return 0;
}
(3)Linux动态链接库的创建与使用
[1]主要概念
动态链接库,和静态函数库不同,它里面的函数并不是执行程序本身的一部分,而是根据执行程序需要按需装入,同时其执行代码可在多个执行程序间共享,节省了空间,提高了效率,具备很高的灵活性,得到越来越多程序员和用户的青睐。那么,在LINUX系统中有无这样的函数库呢?
LINUX的动态链接库不仅有,而且为数不少。在/lib目录下,就有许多以.so作后缀的文件,这就是LINUX系统应用的动态链接库,只不过与WINDOWS叫法不同,它叫so,即Shared Object,共享对象。(在LINUX下,静态函数库是以.a作后缀的)
[2]使用共享函数库
通过一个API来打开一个函数库,寻找符号表,处理错误和关闭函数库。
这一过程涉及的相关库函数如下:
#include <dlfcn.h>
dlopen()
dlopen函数打开一个函数库然后为后面的使用做准备。C语言原形是:
void * dlopen(const char *filename, intflag);
dlopen()函数中,参数flag的值必须是RTLD_LAZY或者RTLD_NOW。
RTLD_LAZY:在dlopen返回前,对于动态库中的未定义的符号不执行解析(只对函数引用有效,对于变量引用总是立即解析)。
RTLD_NOW: 需要在dlopen返回前,解析出所有未定义符号,如果解析不出来,在dlopen会返回NULL,错误为:: undefined symbol: xxxx.......
如果有好几个函数库,它们之间有一些依赖关系的话,例如X依赖Y,那么你就要先加载那些被依赖的函数。例如先加载Y,然后加载X。
dlerror()
通过调用dlerror()函数,我们可以获得最后一次调用dlopen(),dlsym(),或者dlclose()的错误信息。
void * dlsym(void *handle, char *symbol);
使用一个DL函数库的最主要的一个函数就是dlsym(),这个函数在一个已经打开的函数库里面查找给定的符号。
函数中的参数handle就是由dlopen打开后返回的句柄,symbol是一个以NIL结尾的字符串。
dlclose()
dlopen()函数的反过程就是dlclose()函数,dlclose()函数用力关闭一个DL函数库。Dl函数库维持一个资源利用的计数器,当调用dlclose的时候,就把这个计数器的计数减一,如果计数器为0,则真正的释放掉。
编译时候要加入-ldl (指定dl库)
例如
gcc test.c -o test -ldl
例子:
#include <stdio.h>
#include <dlfcn.h>//dlopen() etc
#include <stdlib.h>
double (*cosine)(double); //define function pointer
int main()
{
void *handle;
char *error;
handle = dlopen("/lib/libm.so.6",RTLD_LAZY);//1
if(!handle)
{
fputs(dlerror(),stderr);
exit(1);
}
cosine = dlsym(handle,"cos");//2
if((error = dlerror()) != NULL)
{
fputs(error,stderr);
exit(1);
}
printf("cosine(3.14159/2) = %f\n",cosine(3.14159/2));//3
dlclose(handle);//4
return 0;
}
运行结果:
[3]定义和创建动态链接库