1.动态库程序
(1)动态库特点
运行时独立存在
不会链接到执行程序
使用时加载(使动态库程序运行)
(2)与静态库的比较
由于静态库是将代码嵌入到使用程序中,多个程序使用时,会有多份代码,所以代码的体积会增大。
动态库的代码只需要存在一份,其他程序通过函数地址使用,代码体积小。
静态库发生变化后,新的代码需重新嵌入到执行程序中。
动态库发生变化后,如果库中函数的定义(或地址)未变化,其他使用的程序不需要重新链接。
2.创建动态库
(1)建立项目
(2)添加库程序
(3)库程序导出(偏移地址)- 提供给使用者库中的函数信息
3.动态库使用
(1)隐式链接(使动态库程序运行起来的过程不需要程序员负责)
(2)显式链接(使动态库程序运行起来的过程需要程序员自己负责)
4.动态库函数
(1)实现动态库函数
(2)库函数导出
a.声明导出
使用_declspec(dllexport)导出函数
注意:动态库编译链接后,也会有LIB文件,是作为动态库函数映射使用,与静态库不完全相同。
b.模块定义文件 .def
例如:LIBRARY DLLFunc //库
EXPORTS //库导出表
DLL_Mul @1 //导出的函数
5.库函数的使用
(1)隐式链接
头文件和函数原型
可以在函数原型的定义前,增加_declspec(dllimport),例如_declspec(dllimport)int DLL_Add( .. );
如果库函数使用C格式导出,需要在函数定义前增加extern "C"
b.导入动态库的LIB文件
c.在程序中使用函数
d.隐式链接的情况,DLL可以存放的路径:
与可执行文件在同一目录下
当前工作目录
Windows目录
Windows/System32目录
Windows/System
环境变量PATH指定目录
注意:高版本VC的配置文件
(2)显示链接
a.定义函数指针类型
b.加载动态库(使动态库程序运行)
HMODULE LoadLibrary( LPCTSTR lpFileName); //动态库文件名或全路径
返回DLL实例句柄(HINSTANCE)
c.获取函数地址
FARPROC GetProcAddress( HMODULE hModule, //DLL句柄
LPCSTR lpProcName); //函数名称
成功返回函数地址
d.使用函数
e.卸载动态库
BOOL FreeLibrary( HMODULE hModule); //DLL实例句柄
相关代码:
(1)动态库代码
//导出 _declspec(dllexport) int CPPdll_add(int a, int b) { return a + b; } _declspec(dllexport) int CPPdll_sub(int a, int b) { return a - b; }
(2)使用动态库(函数要换名)
#include "windows.h" #include <iostream> //1.定义函数指针类型 typedef int(*DLL_FUNC)(int, int); int main() { //2.加载动态库 HINSTANCE hDll = LoadLibrary("CPPdll.dll"); std::cout << "hDll:" << hDll << std::endl; //3.获取函数地址 DLL_FUNC myAdd = (DLL_FUNC)GetProcAddress(hDll, "?CPPdll_add@@YAHHH@Z"); std::cout << "myAdd:" << std::hex << myAdd << std::endl; //4.使用函数 int sum = myAdd(5, 6); std::cout << "sum=" << std::dec << sum << std::endl; DLL_FUNC mySub = (DLL_FUNC)GetProcAddress(hDll, "?CPPdll_sub@@YAHHH@Z"); std::cout << "mySub:" << std::hex << mySub << std::endl; int sub = mySub(5, 6); std::cout << "sub=" << std::dec << sub << std::endl; //5.下载动态库 FreeLibrary(hDll); return 0; }
运行结果:
总结:
1.制作动态库
(1)声明导出:_declspec(dllexport),将函数的偏移地址导到了dll的文件头中。
如果是C++编译器,dll文件中记录的是换名之后的函数名对应的偏移地址。
lib文件中记录的仅仅是换名之后的函数名对应的编号。
(2)模块定义文件导出:将函数的偏移地址导到了dll的文件头中。
即便是C++编译器,dll文件头中记录的是未换名的函数名对应的偏移地址。
lib文件中记录的仅仅是未换名之后的函数名对应的编号。
2.使用动态库
(1)隐式链接
操作系统的加载器负责使动态库执行(拿到dll首地址)。
链接器负责到lib文件中拿函数的编号,程序执行起来后,操作系统加载器拿着编号到dll文件头中查询函数的偏移地址。
(2)显示链接
调用LoadLibrary使动态库执行(拿到dll首地址)。
调用GetProcAddress函数,通过函数的名称到dll文件头中查询函数的偏移地址。例如:GetProcAddress( hDll, "CPPDll_add")
3.两种链接方式对比
(1)在库函数定义不变的情况下
隐式链接:由于库函数地址是在程序编译链接时设置的,所以当动态库变化后,使用程序需要重新编译链接。
显示链接:由于库函数地址是在程序执行时动态的从库中查询,所以变化后不需要重新编译链接。
(2)动态库加载时间
隐式链接:动态库是在程序运行时被加载的,当dll不存在时,程序无法启动。
显示链接:动态库只在使用LoadLibrary函数时,才会被加载。