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. 搭建Spring+mybatis报错

    java.lang.ClassCastException: com.sun.proxy.$Proxy12 cannot be cast to com.bdqn.service.impl.UserSer ...

  2. SpringMVC get请求中文乱码

    针对GET请求的编码问题,则需要改tomcat的server.xml配置文件,如下: 原 <Connector connectionTimeout="20000" port= ...

  3. centos中w使用smbclient连接window出现:session setup failed: NT_STATUS_LOGON_FAILURE

    1. 在window中网络->我自己的电脑->能够查看到共享文件,说明window的共享是正常了; 2. 在window中配置共享时,使用的是仅仅同意超级管理员訪问,可是我把超级管理员改名 ...

  4. SQL 约束(Constraints)

    SQL 约束(Constraints) SQL 约束(Constraints) SQL 约束用于规定表中的数据规则. 如果存在违反约束的数据行为,行为会被约束终止. 约束可以在创建表时规定(通过 CR ...

  5. 奥斯卡&#183;王尔德十大经典语录

    10不够真诚是危急的,太真诚则绝对是致命的.--摘自<身为艺术家的评论者> "A little sincerity is a dangerous thing, and a gre ...

  6. IE9版本号下面ajax 跨域问题解决

    ajax跨域请求数据在谷歌火狐我本地IE11都是没问题的. 让測试就发现问题了,IE8下请求不到数据.然后我查看一下自己写的js看有没有不兼容问题.但是都没有啊.为什么就请求不到呢. 我把ajax的e ...

  7. spinlock in linux kernel

    spinlock in linux kernel 作为一种锁机制, spinlock可以制造一段临界区, 同一时刻只有一个线程能进入这个临界区, 从而达到保护数据的目的. semaphore, mut ...

  8. Python爬虫开发【第1篇】【Scrapy shell】

    Scrapy Shell Scrapy终端是一个交互终端,我们可以在未启动spider的情况下尝试及调试代码,也可以用来测试XPath或CSS表达式,查看他们的工作方式,方便我们爬取的网页中提取的数据 ...

  9. ASP.NET MVC中为DropDownListFor设置选中项的方法

    在MVC中,当涉及到强类型编辑页,如果有select元素,需要根据当前Model的某个属性值,让Select的某项选中.本篇只整理思路,不涉及完整代码. □ 思路 往前台视图传的类型是List< ...

  10. Django值聚合,分组,事物,cookie,session

    1,聚合(aggregate):是queryset的一个 终止语句,它返回一个包含键值对的字典,键是的名称是聚合值的标识符,值是计算出来的聚合值,键的名称是按照字段和聚合函数自动生成出来的.用到的内置 ...