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

智能推荐

hdu 1229 还是A+B(水)-程序员宅基地

文章浏览阅读122次。还是A+BTime Limit: 2000/1000 MS (Java/Others)Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 24568Accepted Submission(s): 11729Problem Description读入两个小于10000的正整数A和B,计算A+B。...

http客户端Feign——日志配置_feign 日志设置-程序员宅基地

文章浏览阅读419次。HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息。FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。BASIC:仅记录请求的方法,URL以及响应状态码和执行时间。NONE:不记录任何日志信息,这是默认值。配置Feign日志有两种方式;方式二:java代码实现。注解中声明则代表某服务。方式一:配置文件方式。_feign 日志设置

[转载]将容器管理的持久性 Bean 用于面向服务的体系结构-程序员宅基地

文章浏览阅读155次。将容器管理的持久性 Bean 用于面向服务的体系结构本文将介绍如何使用 IBM WebSphere Process Server 对容器管理的持久性 (CMP) Bean的连接和持久性逻辑加以控制,使其可以存储在非关系数据库..._javax.ejb.objectnotfoundexception: no such entity!

基础java练习题(递归)_java 递归例题-程序员宅基地

文章浏览阅读1.5k次。基础java练习题一、递归实现跳台阶从第一级跳到第n级,有多少种跳法一次可跳一级,也可跳两级。还能跳三级import java.math.BigDecimal;import java.util.Scanner;public class Main{ public static void main(String[]args){ Scanner reader=new Scanner(System.in); while(reader.hasNext()){ _java 递归例题

面向对象程序设计(荣誉)实验一 String_对存储在string数组内的所有以字符‘a’开始并以字符‘e’结尾的单词做加密处理。-程序员宅基地

文章浏览阅读1.5k次,点赞6次,收藏6次。目录1.串应用- 计算一个串的最长的真前后缀题目描述输入输出样例输入样例输出题解2.字符串替换(string)题目描述输入输出样例输入样例输出题解3.可重叠子串 (Ver. I)题目描述输入输出样例输入样例输出题解4.字符串操作(string)题目描述输入输出样例输入样例输出题解1.串应用- 计算一个串的最长的真前后缀题目描述给定一个串,如ABCDAB,则ABCDAB的真前缀有:{ A, AB,ABC, ABCD, ABCDA }ABCDAB的真后缀有:{ B, AB,DAB, CDAB, BCDAB_对存储在string数组内的所有以字符‘a’开始并以字符‘e’结尾的单词做加密处理。

算法设计与问题求解/西安交通大学本科课程MOOC/C_算法设计与问题求解西安交通大学-程序员宅基地

文章浏览阅读68次。西安交通大学/算法设计与问题求解/树与二叉树/MOOC_算法设计与问题求解西安交通大学

随便推点

[Vue warn]: Computed property “totalPrice“ was assigned to but it has no setter._computed property "totalprice" was assigned to but-程序员宅基地

文章浏览阅读1.6k次。问题:在Vue项目中出现如下错误提示:[Vue warn]: Computed property "totalPrice" was assigned to but it has no setter. (found in <Anonymous>)代码:<input v-model="totalPrice"/>原因:v-model命令,因Vue 的双向数据绑定原理 , 会自动操作 totalPrice, 对其进行set 操作而 totalPrice 作为计..._computed property "totalprice" was assigned to but it has no setter.

basic1003-我要通过!13行搞定:也许是全网最奇葩解法_basic 1003 case 1-程序员宅基地

文章浏览阅读60次。十分暴力而简洁的解决方式:读取P和T的位置并自动生成唯一正确答案,将题给测点与之对比,不一样就给我爬!_basic 1003 case 1

服务器浏览war文件,详解将Web项目War包部署到Tomcat服务器基本步骤-程序员宅基地

文章浏览阅读422次。原标题:详解将Web项目War包部署到Tomcat服务器基本步骤详解将Web项目War包部署到Tomcat服务器基本步骤1 War包War包一般是在进行Web开发时,通常是一个网站Project下的所有源码的集合,里面包含前台HTML/CSS/JS的代码,也包含Java的代码。当开发人员在自己的开发机器上调试所有代码并通过后,为了交给测试人员测试和未来进行产品发布,都需要将开发人员的源码打包成Wa..._/opt/bosssoft/war/medical-web.war/web-inf/web.xml of module medical-web.war.

python组成三位无重复数字_python组合无重复三位数的实例-程序员宅基地

文章浏览阅读3k次,点赞3次,收藏13次。# -*- coding: utf-8 -*-# 简述:这里有四个数字,分别是:1、2、3、4#提问:能组成多少个互不相同且无重复数字的三位数?各是多少?def f(n):list=[]count=0for i in range(1,n+1):for j in range(1, n+1):for k in range(1, n+1):if i!=j and j!=k and i!=k:list.a..._python求从0到9任意组合成三位数数字不能重复并输出

ElementUl中的el-table怎样吧0和1改变为男和女_elementui table 性别-程序员宅基地

文章浏览阅读1k次,点赞3次,收藏2次。<el-table-column prop="studentSex" label="性别" :formatter="sex"></el-table-column>然后就在vue的methods中写方法就OK了methods: { sex(row,index){ if(row.studentSex == 1){ return '男'; }else{ return '女'; }..._elementui table 性别

java文件操作之移动文件到指定的目录_java中怎么将pro.txt移动到design_mode_code根目录下-程序员宅基地

文章浏览阅读1.1k次。java文件操作之移动文件到指定的目录_java中怎么将pro.txt移动到design_mode_code根目录下

推荐文章

热门文章

相关标签