博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Windows动态链接库
阅读量:2073 次
发布时间:2019-04-29

本文共 6956 字,大约阅读时间需要 23 分钟。

一、Windows的动态链接库简介

DLL即动态链接库(Dynamic-Link Library)的缩写,相当于Linux下的共享对象。Windows系统中大量采用DLL机制,甚至内核的结构很大程度依赖于DLL机制。Windows下的DLL文件和EXE文件实际上是一个概念,都是PE格式的二进制文件。

1.1 Windows下面的动态链接库与Linux下面的动态链接库的区别

(1)文件后缀不同

Linux动态库的后缀是 .so 文件,而window则是 .dll 文件

(2)文件格式不同

(a)Linux下是ELF格式,即Executable and Linkable Format。在ELF之下,共享库中所有的全局函数和变量在默认情况下都可以被其它模块使用,即ELF默认导出所有的全局符号。

(b)Windows下面是PE格式的文件,即Portable Executable Format。DLL本质上也是PE文件,DLL需要显示地“告诉”编译器需要导出某个符号,否则编译器默认所有的符号都不导出。

(c)动态链接库的文件个数不一样

Linux的动态链接库就只有一个 .so 文件,还有与之对应的头文件,而在Windows下面的动态库有两个文件,一个是引入库(.LIB)文件,一个是动态库(.DLL)文件,需要的头文件(.h)文件。

(1)引入库文件包含被DLL导出的函数的名称和位置,对于导入库而言,其实际的执行代码位于动态库中,导入库只包含了地址符号表等,确保程序找到对应函数的一些基本地址信息。

(2)DLL文件包含实际的函数和数据,应用程序使用LIB文件链接到所需要使用的DLL文件,库中的函数和数据并不复制到可执行文件中,因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,这样当一个或多个应用程序运行是再把程序代码和被调用的函数代码链接起来,从而节省了内存资源。

总结:从上面的说明可以看出,Windows下面所创建的动态链接库DLL和.LIB文件必须随应用程序一起发行,否则应用程序将会产生错误。一般的动态库程序有lib文件和dll文件,lib文件是编译时期连接到应用程序中的,而dll文件才是运行时才会被调用的。

1.2 静态链接库和动态链接库的异同点

如果采用静态链链接库(.lib),lib中的指令最终都会编译到链接该静态库的exe(或dll)文件中,发布软件时,只需要发布exe(或dll)文件,不需要.lib文件。

但是若使用动态链接库(. dll),dll中的指令不会编译到exe文件中,而是在exe文件执行期间,动态的加载和卸载独立的dll文件,需要和exe文件一起发布。

静态链接库不能再包含其他动态链接库或静态链接库,而动态链接库不受此限制,动态链接库中可以再包含其他的动态链接库和静态链接库。

来自:https://blog.csdn.net/w_y2010/article/details/80428067

1.3 动态链接库的优点

1)节省内存和代码重用:当应用程序使用动态链接时,多个应用程序可以共享磁盘上单个DLL副本

2)可扩展性:DLL文件与EXE文件独立,只要接口不变,升级程序只需更新DLL文件不需要重新编译应用程序

3)复用性:DLL的编制与具体的编程语言以及编译器无关,不同语言编写的程序只要按照函数调用约定就可以调用同一个DLL函数

1.4 有下面的代码

  • 头文件framework.h
#pragma oncenamespace mycal{
int add(int a, int b); int sub(int a, int b);}
  • 实现代码 framework.cpp
//framework.cpp#include "framework.h"int mycal::add(int x, int y){
return x + y;}int mycal::sub(int x, int y){
return x - y;}

在Linux下,编译成动态链接库之后,会得到一个 xxx.so 文件,现在只要引入头文件,包含动态库路径,就可以正常使用了,但是上面的代码同样在Windows下面,使用VS2017编译成动态链接库之后,的确不会报错,只会的到一个 xxx.dll 文件,(不是还有一个对应的 xxx.lib文件吗,哪里去了呢?)

然后我们新建一个项目,按照 “头文件路径配置——库文件路径配置”的方法,编写代码,我们也可以调用到add这两个函数,还有语法提示,因为语法提示其实来自于头文件,和库文件没关系,但是编译却不成功了,显示调用的add以及sub都是错误的,这是为什么呢?

这是因为前面说了Windows下面需要显式的告诉编译器,动态库中有哪一些函数是可以导出使用的,上面没有显示说明,即add和sub实际上是不可以使用的,故而会报错,怎么办呢?参见下面。

二、Windows平台之下使用VS2017如何创建动态库

2.1 解决未生成lib文件以及函数没有显式导出的问题

两种方式来决定动态库中到底哪些函数是可以导出供外部直接使用的,以及与此同时生成与dll对应的 .lib 文件。

(1)MSVC编译器提供了关键字_declspec,来指定指定符号的导入导出,即_declspec属性关键字

_declspec(dllexport) 表示该符号是从本DLL导出的符号:这是在定义DLL中的函数等源代码是必须使用的,如果不显式的导出某一些符号,则使用动态链接库虽然没有语法上的错误,但是她无法编译,因为dll中的函数没有暴露出来,故而找不到。

_declspec(dllimport)表示该符号是从别的DLL中导入的:我们在使用动态链接库DLL中暴露出来的函数的时候,可以直接使用暴露的函数,也可以通过显示地导入函数,编译器会产生质量更好的代码。由于编译器确切地知道了一个函数是否在一个DLL中,它就可以产生更好的代码,不再需要间接的调用转接。如下所示:

//显式的导入dll中暴露出来的函数__declspec(dllimport) void func1(void);int main(void){
func1();}

后面会专门讲如何使用 __declspec 来创建动态库。

(2)使用"xxx.def"文件来声明导入和导出符号

我们也可以不使用__declspec 来创建动态库,我们就按照正常的程序编写,如第一章节里面的 myMath.h 和 myMath.cpp 里面的内容,然后显示的添加一个 xxx.def 文件,怎么添加呢?

如下:右击项目/添加/新建项,选择如下的文件:

在这里插入图片描述
默认是使用source.def 我们可以自定义名称。关于这个def文件是如何规定哪些内容是导出的,哪一些是不导出的,这里暂时先不说明了,可以参考下面的几篇文章:

2.2 使用 __declspec 来创建动态库的完整过程

(1)新建一个空项目或者是使用DLL模板都可以。

  • 添加头文件 framework.h
//framework.h#pragma once#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers// Windows Header Files#include 
namespace mycal {
__declspec(dllexport) int add(int a, int b); __declspec(dllexport) int sub(int a, int b);}

注意:实际上就是在需要定义的函数前面添加一个 __declspec(dllexport) 是两个短下划线开头哦!来表示这两个函数是暴露出来可以供直接使用的,没有暴露的函数,在动态链接库中是没办法使用的。

2)实现函数的内容。

  • 定义一个 framework.cpp文件
//framework.cpp#include "framework.h"int mycal::add(int x, int y){
return x + y;}int mycal::sub(int x, int y){
return x - y;}

这个地方和我们平时的实现完全一样,实现的时候不再需要添加 __declspec(dllexport) 了。

(3)生成项目

比如我选择生成 Debug x64位的结果,

生成之后得到如下的结果:

在这里插入图片描述
我们发现有一对配套的 xxx.dll 和 xxx.lib 文件,他们的大小不一样哦!此lib文件只是dll文件中导出函数的声明和定位信息,并不包含函数的实现,因此此lib文件只是在调用对应dll库的工程编译时使用,不需要随exe发布。

三、动态链接库的使用

3.1 静态调用dll

静态调用是由编译系统完成对dll文件的加载和应用程序结束时完成对dll的卸载,当调用某dll的应用程序结束时,则windows系统对该dll的应用记录减1,直到使用该dll的所有应用程序都结束,即对该dll应用记录为0,操作系统会卸载该dll,静态调用方法简单,但不如动态调用适用。

前面说了,window上生成的动态链接库的使用需要三个东西:头文件,dll文件,与dll对应的lib文件。

这里都具备了,现在新建一个项目,如何配置呢?

三步走配置

(1)第一步:配置包含路径——即头文件所在的路径,将头文件复制过来。

在这里插入图片描述

(2)第二步:配置库路径——即lib所在的路径,将Dll_demo_1.lib也复制到当前工程文件夹中。

在这里插入图片描述

(3)第三步:添加链接,——将上面得到的Dll_demo_1.lib添加到链接器:

#pragma comment(lib,"Dll_demo_1.lib")

这一句是告诉编译器与该dll相对应的.lib文件所在的路径和文件名。在生成dll文件时,链接器会自动为其生成一个对应的.lib文件,该文件包含了dll导出函数的符号名和序号(并没有实际的代码)。在应用程序中,.lib文件将作为dll的替代文件参与编译,编译完成后,.lib文件就不需要了。

修改复制过来的 framework.h,将里面的__declspec(dllexport)为__declspec(dllimport):

#pragma once#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers// Windows Header Files#include 
namespace mycal {
__declspec(dllimport) int add(int a, int b); __declspec(dllimport) int sub(int a, int b);}

Source.cpp 代码如下:

#include 
#include
//添加自己定义的头#pragma comment(lib,"Dll_demo_1.lib")int main(){
int x = 50; int y = 10; int a,b; a = mycal::add(x, y); b = mycal::sub(x, y); printf("%d %d\n", a, b); getchar(); return 0;}

现在生成,生成没有错误,但是运行依然会报错,因为运行的时候需动态链接库,所以还需要配置动态链接库。

一共有三种方式,本文采用最简单的方式,直接将动态链接库和可执行exe文件拷贝到一起即可。

在这里插入图片描述
然后运行上面的程序,得到结果如下:

120 80

上面就是整个动态链接库的创建以及使用的过程。

注意

1)可以修改.lib文件的文件名,只要在项目引用它时,使用它目前的名称,便可以正确运行,但不能改变.dll文件的名字,不然也会出现找不到.dll文件的错误

2).dll文件必须和.exe放在一起,.exe文件在哪里.dll文件也得在那里,两者“共存亡”,否则就会出现找不到.dll文件的错误

3)和静态库一样,.dll文件也要和应用程序的位数相对应,要么都是64位的,要么都是32位的,不可交叉使用。

4)静态调用不需要使用Win32API函数来加载和卸载Dll以及获取Dll中导出函数的地址,这是因为当通过静态链接方式编译生成程序时,编译器会将.lib文件中导出函数的函数符号链接到生成的exe文件中,.lib文件中包含的与之对应的dll文件的文件名也被编译存储在exe文件内部,当应用程序运行过程中需要加载dll文件时,windows将根据这些信息查找并加载dll,然后通过符号名实现对dll函数的动态链接,这样,exe将能直接通过函数名调用dll 的输出函数,就像调用程序内部的其他函数一样。

3.2 动态调用dll

动态调用是由程序员调用系统API函数加载和卸载dll,程序员可以决定dll文件何时加载,何时卸载,加载哪个dll文件,将dll文件的使用权完全交给程序员。

1)、新建控制台项目,添加 main.cpp 文件,将刚刚生成的 Dll_demo_1.dll 文件拷贝到项目目录下,main.cpp 代码如下

#include "stdio.h"#include 
typedef int (*lpAddFun)(int ,int );//宏定义函数指针类型int main(){
HINSTANCE hDll;//DLL 句柄 lpAddFun addFun;//函数指针 hDll = LoadLibrary(L"Dll_demo_1.dll");//动态获取dll文件的路径 if (hDll!=NULL) {
addFun =(lpAddFun)GetProcAddress(hDll,"add");//根据函数名在dll文件中获取该函数的地址 if (addFun!=NULL) {
int result =addFun(2,3); printf("2+3=%d",result); } FreeLibrary(hDll); } return 0;}

运行结果:2+3=5

3.2.1 main.cpp分析

语句typedef int (*lpAddFun)(int ,int )定义了一个与add函数接收参数类型和返回值均相同的函数指针类型,随后在main函数中定义了lpAddFun的实例addFun;在函数main中定义了一个DLL HISTANCE句柄实例hDll,通过Win32API函数LoadLibrary动态加载DLL模块并将DLL模块句柄赋给hDll

main函数中通过Win32API函数GetProcAddress得到所加载的DLL模块中函数add的地址并赋值给addFun,经由函数指针addFun进行了对该DLLadd函数的调用;
在完成对dll的调用后,在main函数中通过Win32API函数FreeLibrary释放已加载的DLL模块。

通过以上的分析可知:

(a) 动态调用只需要dll文件即可,不需要对应的.h头文件和.lib文件,一般情况下,只要有dll,就可以调用此dll中的导出函数。
(b) 在调用dll中的函数时,需要知道导出函数的函数签名,若拥有dll对应的头文件,可以参照头文件即可,若没有头文件,使用特定工具也可以得到dll中导出函数的函数签名
(c) DLL需要已某种特定的方式声明导出函数。
(d) 应用程序需要以特定的方式调用DLL的淡出函数。

四、Visual Studio提供了一个命令行工具:Dumpbin

这个工具不需要自己安装,我们通过安装VS,可以直接使用,如下打开VS自带的命令行工具,如下:

在这里插入图片描述
这个工具有什么作用呢?简而言之,它可以查看一个 lib文件 dll文件提供了哪一些函数,暴露出来可供使用的,它还可以查看 exe 文件包含了哪一些静态库和动态库。

查看一下它的帮助信息:

在这里插入图片描述

如何使用呢?举几个简单的例子,以本文所创建的动态库和可执行程序作为演示:

(1)查看Dll_demo_1.dll 暴露出来了哪一些函数可以使用——dumpbin -exports xxxx.dll(xxxx.lib文件也一样的)

F:\OpenCV4.1.1_Test\2020_01_10\calcuDll\calculate\x64\Release>dumpbin -exports calculate.dll

总结:

查看导入:dumpbin -exports 文件名称

查看导出:dumpbin -imports 文件名称


  1. https://blog.csdn.net/qq_33757398/article/details/81545966

你可能感兴趣的文章
算法工程师 面经2019年5月
查看>>
搜索架构师 一面面经2019年6月
查看>>
稻草人手记
查看>>
第一次kaggle比赛 回顾篇
查看>>
leetcode 50. Pow(x, n)
查看>>
leetcode 130. Surrounded Regions
查看>>
【托业】【全真题库】TEST2-语法题
查看>>
博客文格式优化
查看>>
【托业】【新托业全真模拟】疑难语法题知识点总结(01~05)
查看>>
【SQL】group by 和order by 的区别。
查看>>
【Python】详解Python多线程Selenium跨浏览器测试
查看>>
Jmeter之参数化
查看>>
Shell 和Python的区别。
查看>>
Python 列表(list)、字典(dict)、字符串(string)常用基本操作小结
查看>>
Loadrunner之https协议录制回放报错如何解决?(九)
查看>>
python中xrange和range的异同
查看>>
列表、元组、集合、字典
查看>>
【Python】easygui小甲鱼
查看>>
【Python】关于Python多线程的一篇文章转载
查看>>
【Pyton】【小甲鱼】文件
查看>>