DLL全称dynamic linking library.即动态链接库。广泛应用与windows及其他系统中。因此对dll的深刻了解,对计算机软件开发专业人员来说非常重要。

windows中所有API都包含在DLL中。三个最重要的DLL是Kernel32.dll,User32.dll,GDI32.dll。

使用dll的好处:

1:扩展了应用程序的特性。

2:简化了项目管理

可以让不同的开发团队管理不同的模块。

3:有助于节省内存。

一个dll可被多个程序共享。多个程序调用同一个dll内的同一个函数时,系统却只需将该dll加载一次。

4:促进资源共享。

5:促进了本地化

可以使应用程序只包含代码但不包含用户界面组件。

6:有助于解决平台间差异。

使用延迟加载机制,程序仅仅加载需要的函数,使程序可以在老版本的系统中运行,可不是在某些函数不被兼容时拒绝运行。

7:可以用于特殊目的。

如钩子等等。

在应用程序可以调用dll中的函数之前,必须将dll载入进程地址空间。可以通过两种方式实现:一种是通过隐式载入时链接。另一种是显式运行时链接。接下来主要介绍隐式链接的过程。显式链接在DLL高级技术中介绍。

在载入之前,必须要构造DLL文件。下面我们来谈谈dll的构造过程。

1:创建一个头文件。该头文件包含我们想要在dll中导出的函数原型、结构以及符号。构建dll时所有的源文件都必须包含该头文件。另外可执行文件也需要该头文件。

2:创建源文件来实现dll模块中想要导出的函数和变量。该源文件在构造可执行文件时并不需要该源文件。

3:编译器对每个源文件处理,并分别产生一个obj文件。

4:链接所有的obj模块。产生独立的dll映像文件。该文件在构建可执行文件时被使用。

5:如果dll文件中输出了至少一个函数或变量,链接器还会生成lib文件。他只是列出了所有被导出的函数和变量的符号名。为了构建可执行模块,在可执行模块代码链接时,该文件也是必需的。

构建可执行模块:

1:所有源文件中包含dll开发人员创建的dll的头文件。

2:创建源文件。包含所有函数和变量。代码中可以引用dll的函数和变量。

3:为每个源文件产生obj文件。

4:将所有obj文件链接,生成独立的可执行映像文件。该文件中包含所有二进制代码预计全局静态变量。还包含一个导入段,列出了他需要的dll模块的名称,以及可执行文件的二进制代码从中引用的函数和变量的符号名。

执行:

加载程序为新建进程申请一个地址空间区域,然后将可执行模块映射到地址空间中。加载程序解释exe文件的导入段,对导入段中每个导入函数所在的dll,加载程序会在系统中对dll模块进行定位,并将该dll映射到进程的地址空间中。如果dll需要从其他dll导入变量或函数,其他dll也会被映射到进程地址空间,执行类似的操作。将所有dll映射到进程地址空间后,就可以开始运行了。

构建dll模块。

dll中通常只包含函数或变量,并不包含消息循环或创建窗口的代码,因此创建dll文件相对容易。要注意在实际使用中,为了去掉代码的抽象层,应该避免从dll中导出变量。

首先应该创建一个包含想要导出的变量或函数声明的头文件。所有dll的源文件都应该包含这个文件,所有需要导入这些函数和变量的可执行模块的源文件也要包含该文件。

看例子:

  1. ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  2. //Mydll.h   dll头文件。
  3. #ifndef MYLIBAPI
  4. #define MYLIBAPI extern"C" _declspec(dellimport)//用于可执行模块。
  5. #endif
  6. MYLIBAPI      export_variable;
  7. MYLIBAPI   int  export_func();
  8. dll源文件
  9. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  10. #define MYLIBAPI   extern"C"     _declspec(dllexport)
  11. //如果编译器看到一个变量函数或是C++类是使用
  12. //此修饰符修饰的,他就知道应该在dll模块中导出该变量。external “C”不要漏掉哦!!!
  13. //另外要注意,在dll的头文件中,MYLIBAPI一定要在包含dll的
  14. //头文件之前定义
  15. //否则预处理器就会因为未定义MYLIBAPI而将
  16. //MYLIBAPI定义为_desclspec(import)
  17. #include "Mydll.h"
  18. int export_varibale;
  19. //在源文件中的变量定义时,可以不使用_declspec(dllexport)修饰符,
  20. //因为编译器在解析头文件时会记住应该导出那些函数或变量。
  21. int export_func( int a)
  22. {
  23. a++;
  24. return a;
  25. }
  26. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////

可以看到在dll的头文件中使用条件编译#ifdef,对是否在源文件中对MYLIBAPI定义进行了判断。如果没有定义的话就定义它为_declspec(import),这主要是在可执行文件源文件中使用,目的是告诉编译器应该导入哪些符号或函数。这样在需要引用该dll的可执行模块实现文件中就可以不定义_declspec(import).

代码中出现了extern"C"修饰符,只有在编写C++代码时才应该使用该修饰符。因为C++编译器会对函数名和变量名进行改编,而C语言或其他语言不对变量名和函数名进行改编。如果在创建dll的时候使用C++语言实现,编译器对函数名进行了改编,而可执行文件使用c语言实现的。当可执行文件引用dll中的变量或函数时,在链接时就会发现可执行文件引用了一个不存在的符号。通过在C++源代码中使用extern"C“修饰符,就告诉C++编译器不要对函数或变量名进行改编处理。这样用C,C++或是任何语言编写的可执行模块都可以访问该变量或函数。

在链接dll的时候,链接器如果发现有函数或变量被导出,就会生成一个lib文件,该文件列出了该dll导出的符号。在链接任何可执行模块的时候只要可执行模块引用了该dll导出的符号,那么这个lib文件当然是必须的。除了产生这个lib文件之外,在dll文件中还会被嵌入一个导出符号表。被称为导入段,它列出了导出的变量、函数、和类的符号名,还会保存虚拟地址RVA,表示每个符号可以再dll的何处找到。

构建可执行模块:

  1. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  2. //源文件
  3. #include<iostream.h>
  4. #include"Myldll.h"
  5. //在此文件内不应该定义MYLIBAPI,在MYdll.h会检测到,然后将MYLIBAPI定义为
  6. //_declspect(import),当编译器检测到源文件中某个变量或是函数使用该修饰符修饰时,
  7. //他会知道应该从某个dll导入该符号。另外可以不使用该修饰符,但是使用该修饰符会产生
  8. //略微高效的代码。所以建议使用。
  9. #pragma comment(lib,"MyIdlll.lib")//不能省略哦,否则将会导致连接错误!!!!!!
  10. int main(int argc,char**argv)
  11. {
  12. int a=4;
  13. a=export_fucn(a);
  14. std::cout<<a<<std::endl;
  15. }
  16. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

要想使该可执行模块源文件链接成功,还必须提供Mydll生成的lib文件。由于链接器需要将所有的obj文件链接到一起,它必须确定代码中的哪个符号来自哪个dll,提供所有使用到的dll生成的lib文件是必须的。可用在客户代码中使用#pragma comment(lib,"../dll.lib")如果所有的符号都能被解决,那么将会生成可执行模块。

运行可执行模块:
          前面我们提过,启动一个可执行模块时,系统加载程序会为进程申请一块地址空间,接着将可执行文件模块映射到进程地址空间中,然后检查可执行文件的导入段,将所需的dll进行定位并将它们映射到进程的地址空间中。

由于导入段不包含路径只包含名称,所以将在程序必须按照特定的目录搜索DLL文件。

以下是加载程序的搜索顺序:

1:可执行文件目录。

2:windows系统目录。

3:windows目录的System目录。

4:windows目录。

5:进程当前目录。

6:PATH环境变量所列出的目录。

为了防止DLL伪造,windows进行了设定,使对对windows目录的搜索先于应用程序的当前目录。此设置可以通过改变注册表进行改变。

更多关于dll的信息请参考另一篇博文:谈谈dll高级技术。

参考自《windows核心编程》第五版第四部分,以上仅仅是个人总结,如有纰漏,请不吝赐教。

《windows核心编程系列》十七谈谈dll的更多相关文章

  1. 《windows核心编程系列》十九谈谈使用远程线程来注入DLL。

    windows内的各个进程有各自的地址空间.它们相互独立互不干扰保证了系统的安全性.但是windows也为调试器或是其他工具设计了一些函数,这些函数可以让一个进程对另一个进程进行操作.虽然他们是为调试 ...

  2. 《windows核心编程系列》十八谈谈windows钩子

    windows应用程序是基于消息驱动的.各种应用程序对各种消息作出响应从而实现各种功能. windows钩子是windows消息处理机制的一个监视点,通过安装钩子能够达到监视指定窗体某种类型的消息的功 ...

  3. 《Windows核心编程系列》二十谈谈DLL高级技术

    本篇文章将介绍DLL显式链接的过程和模块基地址重定位及模块绑定的技术. 第一种将DLL映射到进程地址空间的方式是直接在源代码中引用DLL中所包含的函数或是变量,DLL在程序运行后由加载程序隐式的载入, ...

  4. 《windows核心编程系列》二谈谈ANSI和Unicode字符集 .

    http://blog.csdn.net/ithzhang/article/details/7916732转载请注明出处!! 第二章:字符和字符串处理 使用vc编程时项目-->属性-->常 ...

  5. 《windows核心编程系列》二十一谈谈基址重定位和模块绑定

    每个DLL和可执行文件都有一个首选基地址.它表示该模块被映射到进程地址空间时最佳的内存地址.在构建可执行文件时,默认情况下链接器会将它的首选基地址设为0x400000.对于DLL来说,链接器会将它的首 ...

  6. 《windows核心编程系列》十六谈谈内存映射文件

    内存映射文件允许开发人员预订一块地址空间并为该区域调拨物理存储器,与虚拟内存不同的是,内存映射文件的物理存储器来自磁盘中的文件,而非系统的页交换文件.将文件映射到内存中后,我们就可以在内存中操作他们了 ...

  7. 《Windows核心编程系列》十四谈谈默认堆和自定义堆

    堆 前面我们说过堆非常适合分配大量的小型数据.使用堆可以让程序员专心解决手头的问题,而不必理会分配粒度和页面边界之类的事情.因此堆是管理链表和数的最佳方式.但是堆进行内存分配和释放时的速度比其他方式都 ...

  8. 【windows核心编程】远程线程DLL注入

    15.1 DLL注入 目前公开的DLL注入技巧共有以下几种: 1.注入表注入 2.ComRes注入 3.APC注入 4.消息钩子注入 5.远线程注入 6.依赖可信进程注入 7.劫持进程创建注入 8.输 ...

  9. 《windows核心编程系列》七谈谈用户模式下的线程同步

    用户模式下的线程同步 系统中的线程必须访问系统资源,如堆.串口.文件.窗口以及其他资源.如果一个线程独占了对某个资源的访问,其他线程就无法完成工作.我们也必须限制线程在任何时刻都能访问任何资源.比如在 ...

随机推荐

  1. Mybatis各种模糊查询(转)

    模糊查询: 工作中用到,写三种用法吧,第四种为大小写匹配查询 1. sql中字符串拼接 SELECT * FROM tableName WHERE name LIKE CONCAT(CONCAT('% ...

  2. 如何使用shell收集linux系统状态,并把结果发给远端服务器

    第一步:收集系统当天状态 load状态 内存状态 cpu状态 jvm相关信息:jstat jstack 网络信息 硬盘信息 第二步:发送到远端服务器 使用curl.wget.定义接口. https:/ ...

  3. 【面试被虐】如何只用2GB内存从20亿,40亿,80亿个整数中找到出现次数最多的数?

    这几天小秋去面试了,不过最近小秋学习了不少和位算法相关文章,例如 [面试现场]如何判断一个数是否在40亿个整数中? [算法技巧]位运算装逼指南 对于算法题还是有点信心的,,,,于是,发现了如下对话. ...

  4. javascript创建对象总结(javascript高级程序设计)

    1.工厂模式 这样的模式抽象创建详细对象的过程.用函数封装特定的接口来创建类. function createStudent(name) { var o = new Object(); o.name ...

  5. MySQL基础笔记(一) SQL简介+数据类型

    MySQL是一个关系型数据库管理系统(RDBMS),它是当前最流行的 RDBMS 之一.MySQL分为社区版和企业版,由于其体积小.速度快.总体拥有成本低,尤其是开放源码这一特点,一般中小型网站的开发 ...

  6. 基于 Vue.js 之 iView UI 框架非工程化实践记要 使用 Newtonsoft.Json 操作 JSON 字符串 基于.net core实现项目自动编译、并生成nuget包 webpack + vue 在dev和production模式下的小小区别 这样入门asp.net core 之 静态文件 这样入门asp.net core,如何

    基于 Vue.js 之 iView UI 框架非工程化实践记要   像我们平日里做惯了 Java 或者 .NET 这种后端程序员,对于前端的认识还常常停留在 jQuery 时代,包括其插件在需要时就引 ...

  7. Ubuntu16.04下安装Tensorflow CPU版本(图文详解)

    不多说,直接上干货! 推荐 全网最详细的基于Ubuntu14.04/16.04 + Anaconda2 / Anaconda3 + Python2.7/3.4/3.5/3.6安装Tensorflow详 ...

  8. ubuntu中查看已安装软件包的方法

    ubuntu中查看已安装软件包的方法: 方法一:在新立得软件包管理器中,打到已安装,便可以看看有多少包被安装. 如果想把这些包的信息复制到一文件里,可用下面的方法. 方法二:在终端输入 sudo dp ...

  9. IDEA及时更新js代码

    需要在Tomcat的设置中为: on ‘update‘ action:当用户主动执行更新的时候更新 快捷键:Ctrl + F9 on frame deactication:在编辑窗口失去焦点的时候更新 ...

  10. nginx性能优化技巧

    前几天买了本高俊峰的<高性能Linux服务器构建实战I>,网上都说运维必备手册,昨天看了目录加小50页感觉还是比较扩充视野的,很多东西在学校是不可能学到的,就是感觉有的地方讲的仍然不是很清 ...