C++编程笔记:dll的生成与使用

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;
}
  • __declspec(dllexport) int add(int a, int b){
        return a + b;
    }

    __declspec(dllexport) int subtract(int a, int b){
        return a - b;
    }
  •  

为了让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函数加载该进程需要的动态链接库的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/352243.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何通过IP地址分辨公网、私网、内网、外网

如何通过IP地址分辨公网、私网、内网、外网内、外网是相对于防火墙而言的,在防火墙内部叫做内网,反之就是外网。在一定程度上外网等同于公网,内网等同于私网。地址为如下3个区域就是处于私网:1:10.*.*.*2:1…

python画动态表情包_真香!一行Python代码,帮你制作小姐姐的表情包,靠谱吗?...

原标题:真香!一行Python代码,帮你制作小姐姐的表情包,靠谱吗?(我的IU女神)对于小姐姐的动态表情包,相必我们大多数人都不会拒绝,而且都会选择默默的将其收藏(不要问我怎么知道的),一…

mongodb分片

mongodb分片: 本次是用三台主机搭建3个集群(主、备、仲裁)作为三个分片,一个集群(主、备、备)做为config服务器,三个mongos单点做路由,每台5个,一共15个。 新建一个mongo…

批量添加PDF帐号目录

本文参考:https://blog.csdn.net/qq_34104395/article/details/78766400然后根据需要整理的。如作者介意请留言,本人会尽快处理! 准备材料: 下载工具FreePic2Pdf(在本博客上传资料上找PDF转换工具包) 找到…

驳斥5条普通流Tropes

我刚读完“ JDK 8收集器的强大功能的一种例外” ,我不得不说我很失望。 Java冠军 Simon Ritter是Oracle的前Java推广者,现在是Oracle的Java传播者,现在是Azul Systems的副CTO(使用JVM的人 )写了它,因此我希…

私网IP如何访问Internet

公网、内网是两种Internet的接入方式。 内网接入方式:上网的计算机得到的IP地址是Inetnet上的保留地址,保留地址有如下3种形式: 10.x.x.x 172.16.x.x至172.31.x.x 192.168.x.x 内网的计算机以NAT(网络地址转换&#xf…

钉钉机器人发送图片 python_python封装钉钉Webhook机器人消息发送逻辑

python封装钉钉Webhook机器人消息发送逻辑,目前仅支持python2。安装pip install dingmsgapi初始化实例from ding_msg_api import MsgClient# Webhook机器人access_tokenmsgClient MsgClient("****************")发送Text消息from ding_msg_api import Te…

[LevelDB] 写批处理过程详解

leveldb的write代码初看瞎搞一堆,细看则实为短小精悍。1 Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) { 2  // -----A begin------- 3 Writer w(&mutex_); 4 w.batch my_batch; 5 w.sync options.sync; 6 w.d…

关于excel vba 使用CopyFromRecordset出现格式问题的解决方法

关于excel vba 使用CopyFromRecordset出现格式问题的解决方法 出现问题的写法: With ActiveSheet .Name k(i) For num 1 To UBound(myArray) .Cells(1, num) myArray(num, 1) Next num .rang…

python histo 改变 bins 大小_在Python中显示具有非常不均匀的bin宽度的直方图

这是直方图为了生成这个图,我做了:bins np.array([0.03, 0.3, 2, 100])plt.hist(m, bins bins, weightsnp.zeros_like(m) 1. / m.size)但是,正如您所注意到的,我想绘制每个数据点的相对频率的直方图,只有3个不同大小的区间:bin1 0.03 – > 0.3bin…

parted工具详解

通常我们用的比较多的一般都是fdisk工具来进行分区,但是现在由于磁盘越来越廉价,而且磁盘空间越来越大;而fdisk工具他对分区是有大小限制的,它只能划分小于2T的磁盘。但是现在的磁盘空间很多都已经是远远大于2T了,甚至…

Python安装pyinstaller模块的错误:NO module name “setuptools“

出现改pyinstaller安装错误常见问题是:pip版本或者setuptools包版本过低。 出现上图提示的错误后,升级一下setuptools包: (1)pip install --upgrade setuptools (2)pip install pyinstaller

jvm ide_预热JVM –超快速生产服务器和IDE

jvm ide几个月前,我正在阅读Java中的复杂事件处理以及实现低延迟的方法。 在我长达一个小时的研究结束时,我发现即使您的应用程序编写正确并且您的方法主要在0(log n)的时间内运行,并且您使用的是某些尖端的硬件解决方…

Python 项目打包成可执行程序命令

一、安装pyinstaller (1)winR输入cmd,打开命令窗口 2)安装pyinstaller,安装指令:pip install pyinstaller 二、打包 1,切换到打包程序目录 例:需要打包程序目录为:D:\pythonfun\useringfunct…

软RAID-mdadm折腾小记

RAID --- 磁盘阵列,简言之,用来提高硬盘的利用率和速度RAID种类(理论):RAID 0 : 读写性能(最少两块硬盘) --- 硬盘使用量是所有硬盘大小之和,性能是所有硬盘之和RAID 1 : 读写性能,冗余性(最少两块硬盘) --- 空间利用率:所有磁盘中最小的那块(n/2); 读性能接近RAID0,写性能较r…

python学习笔记:第19天 类的约束、异常、MD5和logging

目录 一、类的约束二、异常处理:三、MD5加密四、日志(logging模块)一、类的约束 真正写写项目的代码时都是多人协作的,所以有些地方需要约束程序的结构。也就是说,在分配任务之前就应该把功能定义好,然后分…

新ANTLR 4.6的重要更改

自从上一个主要版本发布以来,已经过去了将近一年的时间,推出了新的ANTLR版本: 4.6 。 有很多新闻:新的目标,更好的性能,更好的错误处理以及ANTLR本身开发中的一些改进。 新目标 影响最大的新闻可能是新目标…

strcmp可以比较数组么_C语言数组越界了,后果很严重,如何避免?

素材来源:嵌入式ARM所谓的数组越界,简单地讲就是指数组下标变量的取值超过了初始定义时的大小,导致对数组元素的访问出现在数组的范围之外,这类错误也是 C 语言程序中最常见的错误之一。在 C 语言中,数组必须是静态的。…

MongoDB 问题123

MongoDB 是非关系型数据库中的一种。 出于某些原因,我们用了Mongo。他们说Mongo的最大特点是快。 不过这种快是以空间换时间的代价而得来的。 这个空间代价包括 1.DB至少占用64M(好像是这个数字,因为我们的DB实际往往只有几M,但却要占用那么多&#xff0…

两个excel文档查找相同选项后替换_看似普通的查找和替换功能,用好了,能让你的工作效率翻一番...

关注【新精英充电站】能力提升看得见!在Word中,查找和替换功能是编辑文档时时常要用到的重要功能,它能帮助我们快速将文档或表格中查找到的内容或格式等替换为指定的内容或格式,特别是遇到大量需要修改的相同文字内容或格式时非常…