C/C++动态链接库(dll)文件解析_c++ dll-程序员宅基地

技术标签: C++学习  c++  c语言  mfc  

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文件)

1、使用VS生成动态链接库的步骤:
(1)新建一个win32控制台工程,并在应用程序设置窗口中选择“Dll”选项,附加选项选择“空项目”。如下图:

这里写图片描述

(2)创建完工程之后,添加源文件,在源文件中写上想导出到dll文件的函数。函数声明之前应该加上“_declpec(dllexport)”表示函数输出为动态链接库。除此之外,还要在函数名前面加上调用约定。因为c/c++语言默认的调用约定是“_cdecl”,如果采用“_cdecl”调用约定,可以不用写。如果使用“_stdcall”和“_fastcall”调用约定,则要进行说明。下图是一个简单的例子:

这里写图片描述

        图中有三个函数,分别采用的调用约定是“_cdecl”,”_stdcall”和”_fastcall”。调用约定会给函数名加上一些修饰,不同的调用约定给函数名的修饰是不一样的,因此要慎重地使用调用约定。
(3)编译。在菜单栏上的“生成”中点击“生成解决方案”即可生成动态链接库。如果编译成功,到工程文件夹下面的Debug文件夹里头可以找到后缀名为dll和lib连个文件。其中,lib文件保存着函数的相关定义和索引,其作用类似于头文件,而dll文件是函数的实现部分,是不可缺少的。

2、生成动态链接库时应注意的事项
(1)函数声明前面加上“_declspec(dllexport)”表明函数将输出为动态链接库,是必不可少的,
(2)导出的函数如果不是采用C/C++默认的“_cdecl”的调用约定,则要特别说明。使用调用约定时,应考虑到以后调用该函数的问题,调用时使用的调用约定只有与生成时设置的调用约定相一致时,才能调用。也就是说,如果生成dll文件时,给函数设置的调用约定为“_stdcall”,而调用该函数时使用的调用约定是“_cdecl”,那么将会无法找到该函数。
(3)在相同的调用约定下,采用不同的编译器,对函数名的修饰是不一样的。比如,同是采用”_cdecl”调用约定,C语言和C++语言导出的dll文件中,函数的修饰名是不一样的。如果要C语言风格的dll文件,就要再加上“extern C”进行修饰,或者把源文件名的后缀改为.c。如果是要C++风格的dll文件,则源文件名后缀必须为.cpp。下图是生成C风格的dll文件例子:

这里写图片描述

        前两个函数将会导出为C风格的dll,而后一个函数被导出为C++风格的dll。如果把源文件后缀改为.c,那么所有的函数都会被导出为C风格的dll。

3、隐式调用动态链接库

1、C语言调用C语言的dll文件
如图,有三个函数被导出到dll,前两个是C语言风格的,后一个是C++风格的。C语言是无法用常规方法调用C++风格的dll。

这里写图片描述

(1)新建一个控制台工程,添加一个源文件,并将源文件的后缀改为.c,告诉编译器这是一个C语言程序。
(2)将lib文件和dll文件放在与源文件相同的目录下。
(3)在程序的开头要加上#pragma comment(lib,”mydll.lib”),第一个参数必须是lib,第二个个参数是lib文件的文件名。函数调用前要先声明,函数的声明需要加上调用约定修饰。如下图:

(4)生成解决方案,如果没有错误,运行程序将会输出正确的结果。

2、C++调用C语言的dll
        在C++程序中,要调用C语言的dll,要声明一下调用的函数是C语言风格的。方法是在函数声明时加上 extern “C”修饰。新建一个控制台工程,添加一个cpp文件,源文件中的代码如图所示:

 3、C++调用C++的dll

        C++调用C++的dll,只需在函数声明时加上调用约定修饰。如下图:

 

 4、动态加载dll

        以上的调用dll的方法都是属于静态调用类型的,一般是需要有lib文件的。如果采用动态加载dll,则不需要lib文件,只需dll文件就足够了。动态加载dll需要用到两个函数,一个是LoadLibrary,另一个是GetProcAddress,这两个函数都包含在window.h头文件中。值得注意的是,动态加载dll文件的方法,一般只能调用C语言风格的,且调用约定为“_cdecl”的函数。下图是动态加载dll的例子:

这里写图片描述

        上面说,动态加载dll的方法一般适合调用C风格的、且调用约定为“_cdecl”的函数,那是因为C风格的、且调用约定为“_cdecl”的函数的函数名不会被修饰,源文件写的是什么样子,dll文件中就是什么样子。当然,调用C++风格的,且不是“_cdecl”约定的函数也是可以的,只是很麻烦。由于编译器类型(C或C++)和调用约定都会对函数的名称进行修饰,使得dll文件中的函数名称不再是源文件所写的那样。GetProcAddress函数是通过函数名来查找函数入口的,因此,只要知道dll文件中的函数修饰名,将函数修饰名传给GetProcAddress函数,就可以获得函数的指针。那么如何知道dll文件中函数的修饰名呢?这就需要用到一些分析软件了,比如depends这个软件就可以查看dll文件的函数名称。下图是使用depends软件查看dll中的函数。可以看到Add和Multi函数在dll文件中的修饰名分别是?Add@@YGHHH@Z 和 ?Multi@@YGHHH@Z。

这里写图片描述

        是不是所有的函数都可以通过查找其在dll文件中的修饰名来获取函数指针呢?为此,我做了一些实验,实验未必充分,但也可以得出一些结论:
(1)往GetProcAddress函数中传入函数在dll的修饰名,如果dll中的函数采用的是“_cdecl”调用约定,无论是C风格的还是C++风格的,都不会报错,函数调用的结果也是正确的。下图是调用采用“_cdecl”调用约定的函数:

这里写图片描述

        从实验结果来看,对于调用约定为“_cdecl”的函数,只要能通过depends找到函数的修饰名,就可以调用该函数。函数调用的结果是正确的。

(2)往GetProcAddress函数中传入函数在dll的修饰名,如果dll中的函数采用的是“_stdcall”调用约定,程序运行时会报错,但是忽略错误,调用的结果却是正确的。下图是调用“_stdcall”约定的函数:

 

 从实验结果来看,调用“_stdcall”约定的函数,是会报错的,但结果仍然正确。

(3)往GetProcAddress函数中传入函数在dll的修饰名,如果dll中的函数采用的是“_fastcall”调用约定,那么程序运行不会报错,但调用结果却是错误的!下图是调用“_fastcall”约定的函数:

        很让人郁闷的是,调用“_fastcall”约定的函数,程序运行时不会报错,但是调用的结果却是错得离谱。2+3=-1672607445这是什么鬼?原因不明。

(4)结论:通过使用LoadLibrary函数和GetProcAddress函数来动态加载dll文件,这种方法只适用于调用“_cedcl”约定的函数,只要是采用“_cdecl”约定的,不管是C风格的还是C++风格的,都可以正常地被调用。如果是其他调用约定,无论是C风格还是C++风格的函数,都无法正常调用。

5、两种加载方式对比

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

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

  • 显示加载的方法则可以解决上述问题,dll只有在需要用到的时候才会被加载到内存中。另外,其实采用隐式链接方式访问dll时,在程序启动时也是通过调用LoadLibrary函数加载该进程需要的动态链接库的。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/a15608445683/article/details/122959797

智能推荐

linux里面ping www.baidu.com ping不通的问题_linux桥接ping不通baidu-程序员宅基地

文章浏览阅读3.2w次,点赞16次,收藏90次。对于这个问题我也是从网上找了很久,终于解决了这个问题。首先遇到这个问题,应该确认虚拟机能不能正常的上网,就需要ping 网关,如果能ping通说明能正常上网,不过首先要用命令route -n来查看自己的网关,如下图:第一行就是默认网关。现在用命令ping 192.168.1.1来看一下结果:然后可以看一下电脑上面百度的ip是多少可以在linux里面ping 这个IP,结果如下:..._linux桥接ping不通baidu

android 横幅弹出权限,有关 android studio notification 横幅弹出的功能没有反应-程序员宅基地

文章浏览阅读512次。小妹在这里已经卡了2-3天了,研究了很多人的文章,除了低版本api 17有成功外,其他的不是channel null 就是没反应 (channel null已解决)拜托各位大大,帮小妹一下,以下是我的程式跟 gradle, 我在这里卡好久又没有人可问(哭)![image](/img/bVcL0Qo)public class MainActivity extends AppCompatActivit..._android 权限申请弹窗 横屏

CNN中padding参数分类_cnn “相同填充”(same padding)-程序员宅基地

文章浏览阅读1.4k次,点赞4次,收藏6次。valid padding(有效填充):完全不使用填充。half/same padding(半填充/相同填充):保证输入和输出的feature map尺寸相同。full padding(全填充):在卷积操作过程中,每个像素在每个方向上被访问的次数相同。arbitrary padding(任意填充):人为设定填充。..._cnn “相同填充”(same padding)

Maven的基础知识,java技术栈-程序员宅基地

文章浏览阅读790次,点赞29次,收藏28次。手绘了下图所示的kafka知识大纲流程图(xmind文件不能上传,导出图片展现),但都可提供源文件给每位爱学习的朋友一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长![外链图片转存中…(img-Qpoc4gOu-1712656009273)][外链图片转存中…(img-bSWbNeGN-1712656009274)]

getFullYear()和getYear()有什么区别_getyear和getfullyear-程序员宅基地

文章浏览阅读469次。Date对象取得年份有getYear和getFullYear两种方法经 测试var d=new Date;alert(d.getYear())在IE中返回 2009,在Firefox中会返回109。经查询手册,getYear在Firefox下返回的是距1900年1月1日的年份,这是一个过时而不被推荐的方法。而alert(d.getFullYear())在IE和FF中都会返回2009。因此,无论何时都应使用getFullYear来替代getYear方法。例如:2016年用 getFullYea_getyear和getfullyear

Unix传奇 (上篇)_unix传奇pdf-程序员宅基地

文章浏览阅读182次。Unix传奇(上篇) 陈皓 了解过去,我们才能知其然,更知所以然。总结过去,我们才会知道我们明天该如何去规划,该如何去走。在时间的滚轮中,许许多的东西就像流星一样一闪而逝,而有些东西却能经受着时间的考验散发着经久的魅力,让人津津乐道,流传至今。要知道明天怎么去选择,怎么去做,不是盲目地跟从今天各种各样琳琅满目前沿技术,而应该是去 —— 认认真真地了解和回顾历史。 Unix是目前还在存活的操作系_unix传奇pdf

随便推点

ACwing 哈希算法入门:_ac算法 哈希-程序员宅基地

文章浏览阅读308次。哈希算法:将字符串映射为数字形式,十分巧妙,一般运用为进制数,进制据前人经验,一般为131,1331时重复率很低,由于字符串的数字和会很大,所以一般为了方便,一般定义为unsigned long long,爆掉时,即为对 2^64 取模,可以对于任意子序列的值进行映射为数字进而进行判断入门题目链接:AC代码:#include<bits/stdc++.h>using na..._ac算法 哈希

VS配置Qt和MySQL_在vs中 如何装qt5sqlmysql模块-程序员宅基地

文章浏览阅读952次,点赞13次,收藏27次。由于觉得Qt的编辑界面比较丑,所以想用vs2022的编辑器写Qt加MySQL的项目。_在vs中 如何装qt5sqlmysql模块

【渝粤题库】广东开放大学 互联网营销 形成性考核_画中画广告之所以能有较高的点击率,主要由于它具有以下特点-程序员宅基地

文章浏览阅读1k次。选择题题目:下面的哪个调研内容属于经济环境调研?()题目:()的目的就是加强与客户的沟通,它是是网络媒体也是网络营销的最重要特性。题目:4Ps策略中4P是指产品、价格、顾客和促销。题目:网络市场调研是目前最为先进的市场调研手段,没有任何的缺点或不足之处。题目:市场定位的基本参数有题目:市场需求调研可以掌握()等信息。题目:在开展企业网站建设时应做好以下哪几个工作。()题目:对企业网站首页的优化中,一定要注意下面哪几个方面的优化。()题目:()的主要作用是增进顾客关系,提供顾客服务,提升企业_画中画广告之所以能有较高的点击率,主要由于它具有以下特点

爬虫学习(1):urlopen库使用_urlopen the read operation timed out-程序员宅基地

文章浏览阅读1k次,点赞2次,收藏5次。以爬取CSDN为例子:第一步:导入请求库第二步:打开请求网址第三步:打印源码import urllib.requestresponse=urllib.request.urlopen("https://www.csdn.net/?spm=1011.2124.3001.5359")print(response.read().decode('utf-8'))结果大概就是这个样子:好的,继续,看看打印的是什么类型的:import urllib.requestresponse=urllib.r_urlopen the read operation timed out

分享读取各大主流邮箱通讯录(联系人)、MSN好友列表的的功能【升级版(3.0)】-程序员宅基地

文章浏览阅读304次。修正sina.com/sina.cn邮箱获取不到联系人,并精简修改了其他邮箱代码,以下就是升级版版本的介绍:完整版本,整合了包括读取邮箱通讯录、MSN好友列表的的功能,目前读取邮箱通讯录支持如下邮箱:gmail(Y)、hotmail(Y)、 live(Y)、tom(Y)、yahoo(Y)(有点慢)、 sina(Y)、163(Y)、126(Y)、yeah(Y)、sohu(Y) 读取后可以发送邮件(完..._通讯录 应用读取 邮件 的相关

云计算及虚拟化教程_云计算与虚拟化技术 教改-程序员宅基地

文章浏览阅读213次。云计算及虚拟化教程学习云计算、虚拟化和计算机网络的基本概念。此视频教程共2.0小时,中英双语字幕,画质清晰无水印,源码附件全课程英文名:Cloud Computing and Virtualization An Introduction百度网盘地址:https://pan.baidu.com/s/1lrak60XOGEqMOI6lXYf6TQ?pwd=ns0j课程介绍:https://www.aihorizon.cn/72云计算:概念、定义、云类型和服务部署模型。虚拟化的概念使用 Type-2 Hyperv_云计算与虚拟化技术 教改