1.动态链接库(dll)概述

没接触dll之前觉得它很神秘,就像是一个黑盒子,既不能直接运行,也不能接收消息。它们是一些独立的文件,其中包含能被可执行程序或其他dll调用来完成某项工作的函数,只有在其他模块调用dll中的函数时,dll才发挥作用。
在实际编程中,我们可以把完成某项功能的函数放在一个动态链接库里,然后提供给其他程序调用。像Windows API中所有的函数都包含在dll中,如Kernel32.dll, User32.dll, GDI32.dll等。那么dll究竟有什么好处呢?

1.1 静态库和动态库

  • 静态库:函数和数据被编译进一个二进制文件(扩展名通常为.lib),在使用静态库的情况下,在编译链接可执行文件时,链接器从静态库中复制这些函数和数据,并把它们和应用程序的其他模块组合起来创建最终的可执行文件(.exe)。当发布产品时,只需要发布这个可执行文件,并不需要发布被使用的静态库。
  • 动态库:在使用动态库时,往往提供两个文件:一个引入库(.lib,非必须)和一个.dll文件。这里的引入库和静态库文件虽然扩展名都是.lib,但是有着本质上的区别,对于一个动态链接库来说,其引入库文件包含该动态库导出的函数和变量的符号名,而.dll文件包含该动态库实际的函数和数据。

1.2 使用动态链接库的好处

  1. 可以使用多种编程语言编写:比如我们可以用VC++编写dll,然后在VB编写的程序中调用它。
  2. 增强产品功能:可以通过开发新的dll取代产品原有的dll,达到增强产品性能的目的。比如我们看到很多产品踢动了界面插件功能,允许用户动态地更换程序的界面,这就可以通过更换界面dll来实现。
  3. 提供二次开发的平台:用户可以单独利用dll调用其中实现的功能,来完成其他应用,实现二次开发。
  4. 节省内存:如果多个应用程序使用同一个dll,该dll的页面只需要存入内存一次,所有的应用程序都可以共享它的页面,从而节省内存。

2. dll的创建

dll的创建主要有两种方法:一是使用 __declspec(dllexport) 创建dll,二是使用模块定义(.def)文件创建dll。

2.1 使用 __declspec(dllexport) 创建dll

首先在VS中的Visual C++中创建一个Win32 Project,取名为Dll1。在Application Type中选择DLL,在Additional options中选择Empty project,即创建一个空的动态链接库工程。

然后为工程添加一个C++源文件:Dll1.cpp,假设我要实现的是加法和减法运算,则代码如下:

__declspec(dllexport) int add(int a, int b){return a + b;
}__declspec(dllexport) int subtract(int a, int b){return a - b;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

为了让dll导出函数,需要在每一个需要被导出的函数前面加上标识符:__declspec(dllexport)
利用Build命令生成Dll1动态链接库,这时在Dll1/Debug目录下就会生成.dll文件和.lib文件,这两个文件即为所需的动态链接库的文件。

既然已经有了这个dll文件,是不是就可以在其他程序中访问该dll中的add和subtract函数了呢?必须注意的一点是:应用程序如果想要访问某个dll中的函数,那么这个函数必须是已经被导出的函数。
为了查看一个dll中有哪些导出函数,Visual Studio提供了一个命令行工具:Dumpbin。

2.2 使用Dumpbin命令确认dll的导出函数

首先在命令行中进入到VS的安装目录下,运行一个名为VCVARS32.bat的批处理程序(对于VS2013来说,该bat文件位于\VC\bin目录下),该文件的作用是用来创建VC++使用的环境信息。(注意,当在命令行界面执行VCVARS32.bat文件后,该文件设置的环境信息只在当前命令行窗口生效。)
然后输入dumpbin命令,即可列出该命令的使用方法:

那么想要查看一个dll提供的导出函数,在Dll1.dll文件所在目录下,在命令行中输入下述命令:

dumpbin -exports Dll1.dll

在上图中可以看到我们导出了两个函数,但是导出函数的名称长得很奇怪,add导出函数的名称是“?add@@YAHHH@Z”,subtract导出函数的名称是“?subtrct@@YAHHH@Z”。这是因为在编译链接时,C++会按照自己的规则篡改函数的名称,这一过程称为“名字改编”。这会导致不同的编译器、不同的语言下调用dll发生问题。因此我们希望动态链接库文件在编译时,导出函数的名称不要发生变化。
为了实现这一目的,可以再定义导出函数时加上限定符:extern “C”,如

extern "C" __declspec(dllexport) int add(int a, int b){
//...
}
  • 1
  • 2
  • 3

但是这种方式只能解决C++和C语言之间相互调用时函数命名的问题。为了彻底解决这个问题,可以通过模块定义(.def)文件实现。

2.3 使用模块定义(.def)文件创建dll

使用def文件创建dll的话就不再需要__declspec(dllexport),因此将代码写成最原始的样子:

int add(int a, int b){return a + b;
}int subtract(int a, int b){return a - b;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

同时为工程创建一个后缀名为.def的文件,并添加进工程,编辑其内容为:

LIBRARY Dll1EXPORTS
add
subtract
  • 1
  • 2
  • 3
  • 4
  • 5

其中LIBRARY语句用于指定动态链接库的名称,该名称与生成的动态链接库名称一定要匹配。EXPORTS语句用于表明dll将要导出的函数,以及为这些导出函数指定的符号名。
将该模块定义文件链接到工程中,方法为工程属性页面>链接器>输入>模块定义文件中写入“Dll1.def”。

然后重新Build Solution,并用dumpbin工具查看现在dll导出的函数,可以发现函数的名字改编问题得到了解决!


以上就是创建dll的两种方法,个人比较提倡使用模块定义(.def)文件创建dll,代码简洁的同时还没有名字改编的问题。
接下来我们来看看如何使用创建好的dll。

3. dll的使用

dll的使用也有两种方法,一是隐式链接的方式加载dll,二是显示加载方式加载dll。

3.1 隐式链接方式加载dll

为了更好地展示dll的使用,我首先创建了一个基于MFC的对话框程序,然后为其添加两个按钮。

将生成好的Dll1.dll和Dll1.lib复制到对话框程序所在的文件夹,然后在CXXXDlg.h中注册动态链接库的引入库文件。因为.lib文件包含了Dll1.dll中导出函数的符号名,相当于告诉对话框程序相关函数应该去dll中调用。

#pragma comment(lib,"Dll1.lib")
  • 1

然后在CXXXDlg.cpp中声明外部函数:

_declspec(dllimport) int add(int a, int b);
_declspec(dllimport) int subtract(int a, int b);
  • 1
  • 2

这样我们就可以使用这两个函数了。为两个按钮添加事件响应程序,并添加如下代码:

void CXXXDlg::OnBtnAdd()
{// TODO: Add your control notification handler code hereCString str;str.Format(_T("5 + 3 = %d"), add(5, 3));MessageBox(str);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

运行程序发现可以通过dll的导出函数来实现加法功能了。说明dll可以使用。

3.2 显示加载方式加载dll

另一种是通过LoadLiabrary函数显示加载dll。代码如下。需要注意的是这时候我们不再需要注册.lib文件,也不需要声明外部函数。只要在需要使用的地方调用dll文件即可。

void CXXXDlg::OnBtnSubtract()
{// TODO: Add your control notification handler code hereHINSTANCE hInst;hInst = LoadLibrary(L"Dll1.dll");typedef int(*SUBPROC)(int a, int b);SUBPROC Sub = (SUBPROC)GetProcAddress(hInst, "subtract");CString str;str.Format(_T("5-3=%d"), Sub(5, 3));FreeLibrary(hInst);       //LoadLibrary后要记得FreeLibraryMessageBox(str);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3.3 两种加载方式对比

通过以上的例子,可以看到隐式链接和动态加载两种加载dll的方式各有优点。

  • 隐式链接方式实现简单,一开始就把dll加载进来,在需要调用的时候直接调用即可。但是如果程序要访问十多个dll,如果都采用隐式链接方式加载他们的话,在该程序启动时,这些dll都需要被加载到内存中,并映射到调用进程的地址空间,这样将加大程序的启动时间。而且一般来说,在程序运行过程中只是在某个条件满足的情况下才需要访问某个dll中的函数,如在上述例子中,我只有在点击按钮时才需要访问dll,其他情况下并不需要访问。这样如果所有dll都被加载到内存中,资源浪费是比较严重的。
  • 显示加载的方法则可以解决上述问题,dll只有在需要用到的时候才会被加载到内存中。另外,其实采用隐式链接方式访问dll时,在程序启动时也是通过调用LoadLibrary函数加载该进程需要的动态链接库的。

reference

《VC++深入详解》孙鑫

动态链接库DLL创建及导出函数、调用DLL相关推荐

  1. 调用未知DLL中的导出函数

    不知道诸位看官是否有过这样的经历:在不经意之间发现一个DLL文件,它里边有不少有趣的导出函数--但是由于你不知道如何调用这些函数,所以只能大发感慨而又无能为力焉.固然有些知名的DLL可以直接通过搜索引 ...

  2. 查看dll出的导出函数

    一般项目中调用其他人封装的dll, 如果在确认函数调用及参数无问题的前提下,可以尝试看下dll中是否包含该导出函数,具体查看步骤如下: 1. 点击搜索vs, 打开VS 2017的x64_x86交叉工具 ...

  3. DllImport dll中有些啥函数 及 dll中是否用到了别的dll

    在加载dll的时候不知道dll中有哪些接口怎么办,或者使用别人封装的东西时报出类似于"无法在 DLL"XXX.dll"中找到名为"XXX函数"的入口点 ...

  4. java jni dll路径_Java中Jni调用DLL文件试验

    Java中Jni调用DLL文件试验 下面是小编整理的Java中Jni调用DLL文件试验内容,希望可以为您带来帮助!更多内容请关注应届毕业生考试网! 所有文件均在E:\路径下. 安装jdk1.6.0_0 ...

  5. matlab dll没有头文件,matlab调用dll没有头文件怎么办

    2018-12-08 回答 基于dll形式vc调用matlab函数方法 如果在一matlab中或vc中写好了函数的话,想在对方的环境中使用,直接将对方语言的函数编译成dll是一个很方便的方法,不需要重 ...

  6. C#.NET如何将cs文件编译成dll文件 exe文件 如何调用dll文件

    比如我要把TestDLL.cs文件编译成dll文件,则在命令提示符下,输入下面的命令,生成的文件为TestDLL.dll csc /target:library TestDLL.cs 注意前提是你安装 ...

  7. python库_python使用ctypes库调用DLL动态链接库_python

    最近要使用python调用C++编译生成的DLL动态链接库,因此学习了一下ctypes库的基本使用. ctypes是一个用于Python的外部函数库,它提供C兼容的数据类型,并允许在DLL或共享库中调 ...

  8. python调用dll函数指针_python使用ctypes库调用DLL动态链接库

    最近要使用python调用C++编译生成的DLL动态链接库,因此学习了一下ctypes库的基本使用. ctypes是一个用于Python的外部函数库,它提供C兼容的数据类型,并允许在DLL或共享库中调 ...

  9. MFC创建、调用Dll的方法

    1. MFC创建DLL 1. 打开VS,新建项目,选择"MFC动态链接库",点击"下一步". 2. 设置项目名,路径等参数,点击"创建". ...

最新文章

  1. Arduino可穿戴教程Linux平台下安装Arduino IDE
  2. MybatisPlus入门Lombok的使用
  3. 未封装的扩展程序是什么意思_Android漏洞挖掘第三期:客户端完整性未校验
  4. 聊一聊基于Nacos的metadata完成服务间的AB测试
  5. 【转载】分层开发思想与小笼包
  6. dpkg命令用法、Ubuntu下deb包的解压、打包、安装、卸载及常用命令参数
  7. python实现带头结点的单链表的就地逆置_带头结点的单链表就地逆置 (10 分)...
  8. java遍历d盘所有文件夹_java遍历指定盘符的所有文件和文件夹
  9. python下载离线包
  10. 详细解说Java 技术的新纪元
  11. jupyter notebook报错500 : Internal Server Error
  12. python矩阵转置_矩阵转置python
  13. 分析docker启动MySQL挂载目录提示权限不足Permission denied原因
  14. String intern()方法
  15. PHP做大转盘抽奖的思路,PHP实现大转盘抽奖算法(代码实例)
  16. 万历名士袁宏道的自在人生——独抒性灵,不拘格套
  17. 18级计算机应用基础期中考试题,早安正能量 励志新开始---法学与社会学学院18级新生开展早自习活动...
  18. 实验4-1-12 黑洞数
  19. 区块链难理解?这里有一篇初学者指南
  20. Springboot, link failure、SSL peer shut down incorrectly、Unsupported record version Unknown-0.0问题

热门文章

  1. 科学“干饭”,AI 营养师教你
  2. 数据产品经理=数据+产品经理?
  3. X64安卓模拟器封包拦截发送工具测试(免代理免网卡)
  4. html怎么设计为中文字体,CSS font-family中文字体设置方法
  5. PowerBuilder6.5 调用WebService
  6. Delphi - 2010下安装IOComp
  7. 30G机器人培训全套资料!(300个PPT+400个视频教程)
  8. html5 ajax 文件上传,html5+FormData 实现ajax文件上传
  9. Cadence OrCAD Capture管脚Passive和Power属性功能详细介绍图文教程
  10. fiddler4修改请求和返回数据