1 动态库的特点
(1)运行时独立存在
(2)不会链接到执行程序
(3)使用时加载(使用动态库,必须使动态库执行)
2 动态库和静态库的比较
(1)由于静态库使将代码嵌入到使用程序中,多个程序使用时,会有多份代码,所以代码体积会增大,动态库的代码只需要存在一份,其他程序通过函数地址使用,所以代码体积小
(2)静态库发生变化后,新的代码需要重新链接嵌入到执行程序中(需要重新编译),动态库发生变化后,如果库中的函数的定义(或地址)未变化,其他使用dll的程序不需要重新链接(直接运行即可,不需要重新编译)。
(3)动态库编译链接后,也会生成lib文件,是作为动态库函数映射使用,与静态库不相同
3 动态库的创建
3.1 使用__declspec(dllexport)导出函数
(1)应用程序选择dll,选择空项目
(2)添加头文件和源文件
头文件如下:
#ifndef _DLLTEST_H_ #define _DLLTEST_H_ extern "C" __declspec(dllexport) void MyPrint(); #endif // _DLLTEST_H_
源文件如下:
#include "dlltest.h" #include <iostream> void MyPrint() { std::cout << "it is myprint function" << std::endl; }
注:当使用显示链接(dll函数的使用方法之一)的时候动态库导出函数前面要加extern "C",因为函数名会被编译器改变,导致GetProcAddress函数找不到函数
(3)测试程序如下
#include "stdafx.h" #include <windows.h> int main() { HMODULE hmodule = LoadLibrary("dlltest.dll"); if (hmodule) { typedef void(*LoadProc)(); LoadProc loadproc = (LoadProc)GetProcAddress(hmodule, "MyPrint"); if (loadproc) { loadproc(); FreeLibrary(hmodule); } else { FreeLibrary(hmodule); } } return 0; }
注:测试程序使用显示链接方式加载动态库
3.2 使用模块定义文件.def
(1)应用程序选择dll类型,选择空项目
(2)添加头文件和源文件
头文件如下:
#ifndef _DEFTEST_H_ #define _DEFTEST_H_ void MyPrint(); #endif //_DEFTEST_H_
源文件如下:
#include "deftest.h" #include <iostream> void MyPrint() { std::cout << "def test function" << std::endl; }
(3)添加def文件
(4)编写def文件内容,def文件内容的格式如下:
LIBRARY DllName
EXPORTS
Insert @1
Delete @2
Member @3
Min @4
注:
(1).def 文件中的第一条 LIBRARY 语句不是必须的,但LIBRARY 语句后面的 DLL 的名称必须正确,即与生成的动态链接库的名称必须匹配。此语句将 .def 文件标识为属于 DLL。链接器将此名称放到 DLL 的导入库中。
(2)EXPORTS 语句列出名称,可能的话还会列出 DLL 导出函数的序号值。通过在函数名的后面加上 @ 符和一个数字,给函数分配序号值。当指定序号值时,序号值的范围必须是从 1 到 N,其中 N 是 DLL 导出函数的个数。
def文件的内容如下:
LIBRARY deftest EXPORTS MyPrint @1
(5)测试动态库,测试程序如下:
#include "stdafx.h" #include <windows.h> int main() { HMODULE hmodule = LoadLibrary("deftest.dll"); if (hmodule) { typedef void(*LoadProc)(); LoadProc loadproc = (LoadProc)GetProcAddress(hmodule, "MyPrint"); if (loadproc) { loadproc(); FreeLibrary(hmodule); } else { FreeLibrary(hmodule); } } return 0; }
4 动态库的使用,动态库的使用方法有两种,一种隐式链接,一种显示链接,上面的测试程序使用的都是显示链接
4.1 显示链接动态库
(1)加载动态库
HMODULE LoadLibrary(LPCTSTR lpFileName /*动态库文件名或全路径*/ ); //返回DLL的实例句柄(HINSTANCE)
(2)使用typedef定义函数指针类型
(3)获取函数的绝对地址
FARPROC GetProcAddress( HMODULE hModule, //DLL句柄 LPCSTR lpProcName //函数名称 //可以查出来导出来的相对地址 ); //成功返回函数地址
(4)使用函数
(5)卸载动态库
BOOL FreeLibrary( HMODULE hModule //DLL的实例句柄 );
注:具体使用方法见上述测试程序
4.2 隐式链接动态库
(1)将lib文件,dll文件,头文件拷贝到测试项目目录下
(2)包含头文件,lib文件(可在项目设置链接器里加,也可通过代码显示加载,具体方法见静态库的加载使用)
(3)直接使用库中函数
注:也可不包含头文件,使用语句__declspec(dllimport) void MyPrint();告诉编译器函数声明,一般会在头文件通过条件编译设置不同的值,如下:
#ifndef DLL_H_ #define DLL_H_ #ifdef DLLProvider #define DLL_EXPORT_IMPORT __declspec(dllexport) #else #define DLL_EXPORT_IMPORT __declspec(dllimport) #endif DLL_EXPORT_IMPORT int add(int ,int); #endif
但一般我们在使用过程中,不需要添加__declspec(dllimport) 调用一般函数也可以,所以上面的测试程序就没有使用__declspec(dllimport)导入函数,那是不是__declspec(dllimport)没啥用?
当然不是,当我们使用动态库的类中静态函数时,必须使用__declspec(dllimport)导出函数,否则会出现无法解析的外部符号,一般动态库包含类时,都需要条件编译区分导入导出宏,因为只是记录动态库的基础知识,故不深究,以后有时间再记录这部分的知识。
测试程序很简单,如下:
#include "stdafx.h" // #include "deftest.h" __declspec(dllimport) void MyPrint(); int main() { MyPrint(); return 0; }
注:我已经在项目属性中的链接器里加上lib库,故在代码里没有加载静态库的代码(#pragma comment语句)
(4)隐式链接的情况,dll可以存放的路径:与执行文件在同一个目录下(建议使用),当前工作目录,windows目录,windows/system32目录,windows/system目录,环境变量path指定目录
4.3 隐式链接和显示链接对比
隐式链接,动态库是在程序启动时被加载,当dll不存在时,程序无法启动
显示链接,动态库只在使用loadlibrary函数,才会被加载
建议:调用大公司的库,使用隐式链接,小公司的库使用显示链接
4 dll中类的使用,前面的测试程序都是直接使用函数,当在dll中声明定义类时,也使用_declspec(dllexport) 导出,如下:
class _declspec(dllexport) CMath { ... }; //导出类的成员函数 //类的声明给用户看,类的实现不给用户看。
当使用dll中的类时,使用方法相同,先导入dll中lib,然后定义类的实例,调用类中的函数
5 显示链接动态库注意事项
(1)LoadLibrary加载动态库失败,一般为文件路径错误和字符串格式(项目编码方式使用多字节方式,也可使用TEXT宏包含字符串)问题
(2)GetProcAddress返回空,找不到函数地址,在dll中的函数定义前加上 extern ”C“即可(因为函数名字被编译器改变产生的问题)
(3)64位进程和32位的动态库不可互相调用,当然,32位的进程和64位的动态库也不可调用,两者版本需要统一
6 静态库lib和动态库输出的lib的区别
静态库的lib:lib包含函数代码本身(即包括函数的索引,也包括实现),在编译时直接将代码加入程序当中
动态库的lib:lib包含了函数所在的dll文件和文件中函数位置的信息(索引),函数实现代码由运行时加载在进程空间中的dll提供