<2017年12月>
262728293012
3456789
10111213141516
17181920212223
24252627282930
31123456

《windows核心编程系列》十七谈谈dll

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的源文件都应该包含这个文件,所有需要导入这些函数和变量的可执行模块的源文件也要包含该文件。

    看例子:

     

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


 

           可以看到在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的何处找到。

   构建可执行模块:

    

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


 

           要想使该可执行模块源文件链接成功,还必须提供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核心编程》第五版第四部分,以上仅仅是个人总结,如有纰漏,请不吝赐教。

posted @ 2018-05-18 09:16  史D芬周  阅读(400)  评论(0编辑  收藏  举报