现在的位置: 首页 > 综合 > 正文

Windows核心编程:DLL基础

2017年05月23日 ⁄ 综合 ⁄ 共 2606字 ⁄ 字号 评论关闭

作者:shenzi

链接:http://blog.csdn.net/shenzi

Windows核心编程:DLL基础

   
态链接库(dynamic-link
library,DLL)一直以来都是Windows操作系统的基石。Windows应用程序编程接口(API)提供的所有函数都包含在DLL中。其中三
个最重要的DLL分别是:Kernel32.dll,包含的函数用来管理内存、进程以及线程;User32.dll,包含的函数用来执行与用户界面相关的
任务,如创建窗口和发送消息;GDI32.dll,包含的函数用来绘制图像和显示文字。
     下面是为什么要使用DLL的一些理由:

  • 它们扩展了应用程序的特性
  • 它们简化了项目管理
  • 它们有助于节省内存
  • 它们促进了资源的共享  

  • 它们促进了本地化
  • 它们有助于解决平台间的差异
  • 它们可以用于特殊目的 

1.DLL和进程的地址空间


   
建DLL通常比创建应用程序容易,因为DLL通常由一组可供任何应用程序使用的独立函数组成。在DLL中,通常没有用来处理消息循环或创建窗口的代码。
DLL只不过是一组源代码模块,每个模块包含一些可供应用程序(可执行文件)或其他DLL调用的函数。在创建DLL时,我们必须给连接器指定/DLL开
关。


    在应用程序(或其他DLL)能够调用一个DLL中的函数之前,必须将该DLL的文件映像映射到调用进程的地址空间中。我们可以通过两种方法来达到这一目的:隐式载入时连接
(implicit load-time linking)或显示运行时链接
(explicit run-time linking)。


    一旦系统将一个DLL的文件映像映射到调用进程的地址空间中之后,进程中的所有线程就可以调用该DLL种的函数了。事实上,该DLL几乎完全丧失了它的DLL身份:对进程中的线程来说,该DLL中的代码和数据就像是一些附加的代码和数据,碰巧被放在进程地址空间中。
     当一个模块提供一个内存分配函数的时候,它必须同时提供另一个用来释放内存的函数。
2.纵观全局


    为了完全理解DLL的工作方式,了解我们和系统是如何使用DLL的,让我们先纵览以下全局。图1概括了各组件是如何结合到一起的。

    

图1:DLL创建过程及应用程序隐式连接到DLL的过程

构建DLL需要以下步骤:


    (1)必须先创建一个头文件,在其中包含我们想要在DLL中导出的函数原型、结构以及符号。在构建可执行文件的时候需要用到同一个头文件。

    (2)创建C/C++源文件来实现想要在DLL模块中导出的函数和变量。由于在构建可执行模块的时候不需要这些源文件,因此创建该DLL得公司可以将这些源代码作为公司的机密。
     (3)在构建该DLL模块的时候,编译器会对每个源文件进行处理并产生一个.obj模块(每个源文件对应一个.obj模块)。
     (4)当所有.obj模块都创建完毕后,连接器会将所有.obj模块的内容合并起来,产生一个单独的DLL映像文件。这个映像文件包含DLL中所有的二进制代码以及全局静态变量。为了执行可执行模块,这个文件时必须的。
  
  (5)如果链接器检测到DLL的源文件输出了至少一个函数或变量,那么链接器还会产生一个.lib文件。这个.lib文件非常小,这是因为它并不包含任何函数或变量。它只是列出了所有被导出的函数和变量的符号名。为了构建可执行模块,这个文件时必须的。
一旦创建了DLL模块,我们将可以通过下列步骤来构建可执行模块:
    

(1)在所有引用了导出的函数、变量、数据结构或符号的源文件中,必须包含由DLL的开发人员所创建的头文件。
     (2)创建C/C++源文件来实现想要包含在可执行模块中的函数和变量。当然,代码可以引用在DLL的头文件中定义的函数和变量。
     (3)在构建可执行模块的时候,编译器会对每个源文件进行处理并产生一个.obj模块(每个源文件对应一个.obj模块)。
  
  (4)链接器将所有.obj模块的内容合并起来,产生一个单独的可执行映像文件。这个映像文件包含了可执行文件中所有的二进制代码以及全局/静态变量。该可执行模块还包含一个导入段(import section),其中列出了所有它需要的DLL模块的名称。
     一旦DLL和可执行模块都已构建完毕,进行就可以执行了。当我们试图运行可执行模块的时候,操作系统的加载程序会执行下面的步骤。
  
  (5)加载程序先为新的进程创建一个虚拟地址空间,并将可执行模块映射到新进程的地址空间中。加载程序接着解析可执行模块的导入段。对导入段中列出的每个DLL,加载程序会在用户的系统中对该DLL模块进行定位,并将该DLL映射到进程的地址空间中。注意,由于DLL模块可以从其它DLL模块中导入函数和变量,因此DLL模块可能有自己的导入段并需要将它所需的DLL模块映射到进程的地址空间中。
构建DLL模块


    
一个DLL可以导出变量、函数或C++类来供其它模块使用。在实际开发中,我们应该避免从DLL中导出变量。此外,只有当导出C++类的模块使用的编译器与导入C++类的模块使用的编译器由一家厂商提供时,我们才可以到处C++类。
     让DLL的构建者和可执行模块的构建者使用同一个头文件可以使维护变得更加容易。
     一个实例:


DLL头文件


DLL源文件


可执行模块源文件

    

意MYLIBAPI的定义:DLL源文件中在包含DLL头文件之前定义了 MYLIBAPI为
_declspec(dllexport),所以在编译DLL源文件的时候,它就知道应该在生成的DLL模块中导出该变量、函数或C++类。而可执行文件
不应该在包含DLL头文件之前定义MYLIBAPI,由于MYLIBAPI未定义,因此头文件会将MYLIBAPI定义为
_declspec(dllimport),这样编译器就知道该可执行文件的源文件要从DLL模块中导入一些变量和函数。




    由于导入段只包含DLL的名称,不包含DLL的路径,因此加载程序必须在用户磁盘上搜索DLL。下面是夹在程序的搜索顺序:
  • 包含可执行文件的目录;
  • Windows的系统目录,该目录可以通过GetSystemDirectory得到;
  • 16位的系统目录,即Windows目录中的System子目录;
  • Windows目录,该目录可以通过GetWindowsDirectory得到;
  • 进程的当前目录;
  • PATH环境变量中所列出的目录。  



 

抱歉!评论已关闭.