vs2017中dll的使用
动态链接库(dll)
Windows下有静态链接(lib)库和动态链接库(dll)两种共享代码的方式。
本文将介绍dll的应用场景,以及在vs2017平台下的生成和使用。
# [What] dll是什么
动态链接库(Dynamic Link Library)又称为“应用程序扩展”,在windows系统中,大多数应用程序并非仅有一个可执行文件exe,同时也包含一些相对独立(模块化)的dll文件。dll中存放函数代码实现,exe中存放dll中相应函数代码的地址,而且dll中的代码可以被多个exe调用而在内存中仅保留一份拷贝,从而节省了内存空间。
# [How] 如何生成dll
# 1. 新建一个dll项目
选择“具有导出项的(DLL)动态链接库”,vs会帮我们自动创建与项目同名的.cpp文件和.h文件,并在.h文件中定义好相关导出符号;如果选择“动态链接库(DLL)”则不会创建上述文件。
创建完成后,可以看到vs已经帮我们完成导出符号和预处理器的定义:
# 2. dll两种导出方式
vs官方文档中提供了两种方式可以导出dll中的函数:
模块定义文件(.def):通用性(指给其他语言eg. Java、C#调用)好,但操作相对复杂;
关键字
__declspec(dllexport)
:操作简单,但通用性较差。可见,vs创建dll项目时默认使用了该方式。
下面依次介绍这两种导出方式。
# 3. 模块定义文件(.def)
新建.def文件
vs会自动添加.def文件为链接器输入:
实现一个dll函数
模块定义语句规则
模块定义语句有许多规则,这里只用到
EXPORTS
关键字:编写.def文件如下:
# 4. 关键字__declspec(dllexport)
分别以C++和C的方式实现两个导出函数,后面会看到这两个函数的不同之处:
# 5. 生成结果
这里除了MYDLL.dll
还生成了MYDLL.lib
文件,它是dll的导入库,用于隐式链接dll。
从Visual Studio 命令提示符
处启动DUMPBIN工具,执行dumpbin -exports path\to\dll
命令分析生成的dll,查看编译器产生的函数修饰名:
可以得出以下两个结论:
VC++编译器会针对C++函数使用名称修饰,而不会修饰以C方式声明的函数(其实也修饰了,在函数名前面加了一个下划线前缀,这是C语言的函数调用约定默认为
__cdecl
所导致);.def文件的作用就是将被修饰过的C++函数重命名,使其可以被其他语言调用,具有通用性。
# [How] 如何调用dll
新建一个控制台应用,在其中调用上述生成的dll。
调用dll有两种链接方式:隐式链接和显式链接,无论哪种方式都要求将dll和exe放在同一目录下。
# 1. 隐式链接
隐式链接需要三个文件:.h文件、.lib文件 和 .dll文件。
可以使用上一篇文章介绍的项目配置或编译语句的方式将.h文件和.lib文件添加到项目中;如果dll项目和控制台项目在同一解决方案下,也可以采取直接引用的方式将dll项目添加到控制台项目。
这里使用编译语句的方式,调用结果如下:
# 2. 显式链接
显式链接只需要一个文件:.dll文件。
所谓显式链接,就是直接调用WIN32 API函数
LoadLibrary
、GetProcAddress
和FreeLibrary
显式地装载、卸载dll。它们的作用如下:调用代码:
#include <iostream>
#include <Windows.h> // 必要头文件
void dllLinkExplicitly() {
typedef void(*LPFNDLLFUNC)(std::ostream &);
// equal to
// using LPFNDLLFUNC = void(*)(std::ostream &);
HINSTANCE hDLL; // Handle to DLL
LPFNDLLFUNC lpfnMyDLLWithDllExport; // Function pointer
LPFNDLLFUNC lpfnMyDLLWithExternC; // Function pointer
LPFNDLLFUNC lpfnMyDLLWithDefFile; // Function pointer
hDLL = LoadLibrary("MyDLL.dll");
if (hDLL != NULL) {
lpfnMyDLLWithDllExport = (LPFNDLLFUNC)GetProcAddress(hDLL, "fnMyDLLWithDllExport");
lpfnMyDLLWithExternC = (LPFNDLLFUNC)GetProcAddress(hDLL, "fnMyDLLWithExternC");
lpfnMyDLLWithDefFile = (LPFNDLLFUNC)GetProcAddress(hDLL, "fnMyDLLWithDefFile");
if (!lpfnMyDLLWithDllExport) { // handle the error
std::cout << "fnMyDLLWithDllExport load error.\n";
}
else { // call the function
lpfnMyDLLWithDllExport(std::cout);
}
if (!lpfnMyDLLWithExternC) {
std::cout << "fnMyDLLWithExternC load error.\n";
}
else {
lpfnMyDLLWithExternC(std::cout);
}
if (!lpfnMyDLLWithDefFile) {
std::cout << "fnMyDLLWithDefFile load error.\n";
}
else {
lpfnMyDLLWithDefFile(std::cout);
}
}
FreeLibrary(hDLL);
}
int main() {
dllLinkExplicitly();
std::cout << "this is my exe.\n";
system("PAUSE");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
调用结果:
可以看出,由于
fnMyDLLWithDllExport
的函数名被编译器修饰过,已经无法通过原来的函数名调用。这也暴露出显式链接的一个弊端:要求开发人员必须清楚地知道调用函数的导出名称和传参格式。
# 3. 小结
隐式链接是程序载入内存时加载所需的dll,且该dll随主进程始终占用内存。
优点:配置好.h文件和.lib文件的路径后调用方式简单直接;
缺点:需要借助.h文件和.lib文件获得所需函数的入口(如使用
#pragma comment
语句),注意这里的lib是dll导入库,与静态链接库lib有所不同。
显式链接是在程序运行过程中需要时使用
LoadLibrary
加载,不需要时则使用FreeLibrary
将其卸载。如果加载时该dll已经在内存,则只需将其引用计数加1,如果其引用计数为0则移出内存。优点:不需要.h文件和.lib文件,可以动态加载、卸载dll;
缺点:调用WIN32 API函数增加了编码量,需要开发人员对dll很了解。
# [Why] dll的优缺点
优点:相比lib加载全量代码到exe中,dll节省了内存空间资源;当程序更新时,仅需要以补丁或扩展的形式发布新的dll即可。
缺点:dll需随exe一起发布,否则程序在运行时会产生错误。
# [Summary] lib和dll的比较
# | lib | dll |
---|---|---|
调用时需要的文件 | .h 和.lib | 隐式链接需要.h 、.lib 和.dll ,显式链接仅需要.dll |
多次调用同一函数 | 内存中有多份拷贝 | 只有一份拷贝 |
能否再包含其他lib或dll | ❌ | ✔️ |
程序发布是否需要提供 | ❌ | ✔️ |
程序后续更新 | 全量更新 | 增量更新 |
适用场景 | 一次性交付,不保证后续更新 | 持续性交付,系统有模块化要求 |
# [Github] 代码
项目实例均在vs2017上测试,并上传至GitHub: https://github.com/lyandut/EXE_LIB_DLL (opens new window)
# [Reference] 参考
https://www.cnblogs.com/alantu2018/p/8470976.html (opens new window) https://www.cnblogs.com/TenosDoIt/p/3203137.html (opens new window) https://blog.csdn.net/weixin_43118068/article/details/88760001 (opens new window) https://blog.csdn.net/cynophile/article/details/79749524 (opens new window) https://blog.csdn.net/freeking101/article/details/104632710 (opens new window) https://blog.csdn.net/Hilavergil/article/details/78544424 (opens new window)