Windows核心编程019.pdf
《Windows核心编程019.pdf》由会员分享,可在线阅读,更多相关《Windows核心编程019.pdf(14页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、下载第1 9章D L L基础自从M i c r o s o f t公司推出第一个版本的Wi n d o w s操作系统以来,动态链接库(D L L)一直是这个操作系统的基础。Windows API中的所有函数都包含在 D L L中。3个最重要的 D L L是K e r n e l 3 2.d l l,它包含用于管理内存、进程和线程的各个函数;U s e r 3 2.d l l,它包含用于执行用户界面任务(如窗口的创建和消息的传送)的各个函数;G D I 3 2.d l l,它包含用于画图和显示文本的各个函数。Wi n d o w s还配有若干别的 D L L,它们提供了用于执行一些特殊任务的函
2、数。例如,A d v A P I 3 2.d l l包含用于实现对象安全性、注册表操作和事件记录的函数;C o m D l g 3 2.d l l包含常用对话框(如File Open和File Save);C o m C t l 3 2.D L L则支持所有的常用窗口控件。本章将要介绍如何为应用程序创建D L L。下面是为什么要使用D L L的一些原因:它们扩展了应用程序的特性。由于 D L L能够动态地装入进程的地址空间,因此应用程序能够在运行时确定需要执行什么操作,然后装入相应的代码,以便根据需要执行这些操作。例如,当一家公司开发了一种产品,想要让其他公司改进或增强该产品的功能时,那么就可
3、以使用D L L。它们可以用许多种编程语言来编写。可以选择手头拥有的最好的语言来编写 D L L。也许你的应用程序的用户界面使用Microsoft Visual Basic编写得最好,但是用C+来处理它的商用逻辑更好。系统允许Visual Basic程序加载C+DLL、Cobol DLL和Fortran DLL等。它们简化了软件项目的管理。如果在软件开发过程中不同的工作小组在不同的模块上工作,那么这个项目管理起来比较容易。但是,应用程序在销售时附带的文件应该尽量少一些。我知道有一家公司销售的产品附带了 1 0 0个D L L每个程序员最多有 5个D L L。这样,应用程序的初始化时间将会长得吓
4、人,因为系统必须打开 1 0 0个磁盘文件之后,程序才能执行它的操作。它们有助于节省内存。如果两个或多个应用程序使用同一个 D L L,那么该D L L的页面只要放入R A M一次,所有的应用程序都可以共享它的各个页面。C/C+运行期库就是个极好的例子。许多应用程序都使用这个库。如果所有的应用程序都链接到这个静态库,那么s p r i n t f、s t r c p y和m a l l o c等函数的代码就要多次存在于内存中。但是,如果所有这些应用程序链接到DLL C/C+运行期库,那么这些函数的代码就只需要放入内存一次,这意味着内存的使用将更加有效。它们有助于资源的共享。D L L可以包含对
5、话框模板、字符串、图标和位图等资源。多个应用程序能够使用D L L来共享这些资源。它们有助于应用程序的本地化。应用程序常常使用 D L L对自己进行本地化。例如,只包含代码而不包含用户界面组件的应用程序可以加载包含本地化用户界面组件的 D L L。它们有助于解决平台差异。不同版本的 Wi d n o w s配有不同的函数。开发人员常常想要调用新的函数(如果它们存在于主机的Wi n d o w s版本上的话)。但是,如果你的源代码包含第四部分动态链接库了对一个新函数的调用,而你的应用程序将要在不能提供该函数的 Wi n d o w s版本上运行,那么操作系统的加载程序将拒绝运行你的进程。即使你实
6、际上从不调用该函数,情况也是这样。如果将这些新函数保存在 D L L中,那么应用程序就能够将它们加载到 Wi n d o w s的老版本上。当然,你仍然可以成功地调用该函数。它们可以用于一些特殊的目的。Wi n d o w s使得某些特性只能为 D L L所用。例如,只有当D L L中包含某个挂钩通知函数的时候,才能安装某些挂钩(使用 S e t Wi n d o w s H o o k E x和S e t Wi n E v e n t H o o k来进行安装)。可以通过创建必须在 D L L中生存的C O M对象来扩展Windows Explorer的外壳程序。对于可以由We b浏览器加载
7、的、用于创建内容丰富的We b页的A c t i v e X控件来说,情况也是一样.19.1 DLL与进程的地址空间创建D L L常常比创建应用程序更容易,因为D L L往往包含一组应用程序可以使用的自主函数。在D L L中通常没有用来处理消息循环或创建窗口的支持代码。D L L只是一组源代码模块,每个模块包含了应用程序(可执行文件)或另一个 D L L将要调用的一组函数。当所有源代码文件编译后,它们就像应用程序的可执行文件那样被链接程序所链接。但是,对于一个D L L来说,你必须设定该连链程序的/D L L开关。这个开关使得链接程序能够向产生的 D L L文件映像发出稍有不同的信息,这样,操
8、作系统加载程序就能将该文件映像视为一个 D L L而不是应用程序。在应用程序(或另一个D L L)能够调用D L L中的函数之前,D L L文件映像必须被映射到调用进程的地址空间中。若要进行这项操作,可以使用两种方法中的一种,即加载时的隐含链接或运行期的显式链接。隐含链接将在本章的后面部分介绍,显式链接将在第 2 0章中介绍。一旦D L L的文件映像被映射到调用进程的地址空间中,D L L的函数就可以供进程中运行的所有线程使用。实际上,D L L几乎将失去它作为 D L L的全部特征。对于进程中的线程来说,D L L的代码和数据看上去就像恰巧是在进程的地址空间中的额外代码和数据一样。当一个线程
9、调用D L L函数时,该D L L函数要查看线程的堆栈,以便检索它传递的参数,并将线程的堆栈用于它需要的任何局部变量。此外,D L L中函数的代码创建的任何对象均由调用线程所拥有,而D L L本身从来不拥有任何东西。例如,如果Vi r t u a l A l l o c函数被D L L中的一个函数调用,那么将从调用线程的进程地址空间中保留一个地址空间的区域,该地址空间区域将始终处于保留状态,因为系统并不跟踪 D L L中的函数保留该区域的情况。保留区域由进程所拥有,只有在线程调用 Vi r t u a l F r e e函数或者进程终止运行时才被释放。如你所知,可执行文件的全局变量和静态变量不
10、能被同一个可执行文件的多个运行实例共享。Windows 98能够确保这一点,方法是在可执行文件被映射到进程的地址空间时为可执行文件的全局变量和静态变量分配相应的存储器。Windows 2000确保这一点的方法是使用第1 3章介绍的写入时拷贝(c o p y-o n-w r i t e)机制。D L L中的全局变量和静态变量的处理方法是完全相同的。当一个进程将D L L的映像文件映射到它的地址空间中去时,系统将同时创建全局数据变量和静态数据变量的实例。注意必须注意的是,单个地址空间是由一个可执行模块和若干个 D L L模块组成的。这些模块中,有些可以链接到静态版本的 C/C+运行期库,有些可以链
11、接到一个 D L L版本的C/C+运行期库,而有些模块(如果不是用 C/C+编写的话)则根本不需要C/C+运行期库。许多开发人员经常会犯一个常见的错误,因为他们忘记了若干个C/C+运行期库可以存在于单个地址空间中。请看下面的代码:464计计第四部分动态链接库下载那么你是怎么看待这个问题的呢?上面这个代码能够正确运行吗?D L L函数分配的内存块是由E X E的函数释放的吗?答案是可能的。上面显示的代码并没有为你提供足够的信息。如果E X E和D L L都链接到D L L的C/C+运行期库,那么上面的代码将能够很好地运行。但是,如果两个模块中的一个或者两个都链接到静态C/C+运行期库,那么对fr
12、ee函数的调用就会失败。我经常看到编程人员编写这样的代码,结果都失败了。有一个很方便的方法可以解决这个问题。当一个模块提供一个用于分配内存块的函数时,该模块也必须提供释放内存的函数。让我们将上面的代码改写成下面的样子:这个代码是正确的,它始终都能正确地运行。当你编写一个模块时,不要忘记其他模块中的函数也许没有使用C/C+来编写,因此可能无法使用m a l l o c和f r e e函数进行内存的分配。应该注意不要在代码中使用这些假设条件。另外,在内部调用 m a l l o c和f r e e函数时,这个原则对于C+的n e w和d e l e t e操作符也是适用的。19.2 DLL的总体运
13、行情况为了全面理解D L L是如何运行的以及你和系统如何使用D L L,让我们首先观察一下D L L的整个运行情况。图1 9-1综合说明了它的所有组件一道配合运行的情况。现在要重点介绍可执行模块和 D L模块之间是如何隐含地互相链接的。隐含链接是最常用的链接类型。Wi n d o w s也支持显式链接(第2 0章介绍这个问题)。在图1 9-1中你可以看到,当一个模块(比如一个可执行文件)使用D L L中的函数或变量时,将有若干个文件和组件参与发挥作用。为了简单起见,我将“可执行模块”称为来自 D L L的输第19章 DLL 基础计计465下载入函数和变量,将“D L L模块”称为用于可执行模块
14、的输出函数和变量。但是要记住,D L L模块能够(并且确实常常)输入包含在其他D L L模块中的函数和变量。图19-1 应用程序如何创建和隐含链接D L L的示意图若要创建一个从D L L模块输入函数和变量的可执行模块,必须首先创建一个 D L L模块。然后就可以创建可执行模块。若要创建D L L模块,必须执行下列操作步骤:1)首先必须创建一个头文件,它包含你想要从D L L输出的函数原型、结构和符号。D L L的所有源代码模块均包含该头文件,以帮助创建 D L L。后面将会看到,当创建需要使用D L L中包含的函数和变量的可执行模块(或多个模块)时,也需要这个头文件。2)要创建一个C/C+源
15、代码模块(或多个模块),用于实现你想要在D L L模块中实现的函数和变量。由于这些源代码模块在创建可执行模块时是不必要的,因此创建 D L L的公司能够保466计计第四部分动态链接库下载创造DLL:1)建立带有输出原型/结构/符号的头文件。2)建立实现输出函数/变量的C/C+源文件。3)编译器为每个C/C+源文件生成.obj模块。4)链接程序将生成DLL的.obj模块链接起来。5)如果至少输出一个函数/变量,那么链接程序也生成lib 文件。创造EXE:6)建立带有输入原型/结构/符号的头文件。7)建立引用输入函数/变量的C/C+源文件。8)编译器为每个C/C+源文件生成.obj源文件。9)链接
16、程序将各个.obj模块链接起来,产生一个.exe文件(它包含了所需要DLL模块的名字和输入符号的列表)。运行应用程序:10)加载程序为.exe 创建地址空间。11)加载程序将需要的DLL加载到地址空间中进程的主线程开始执行;应用程序启动运行。编译器编译器链接程序链接程序编译器编译器编译器编译器护公司的秘密。3)创建D L L模块,将使编译器对每个源代码模块进行处理,产生一个.o b j模块(每个源代码模块有一个.o b j模块)。4)当所有的.o b j模块创建完成后,链接程序将所有.o b j模块的内容组合在一起,产生一个D L L映象文件。该映像文件(即模块)包含了用于 D L L的所有二
17、进制代码和全局/静态数据变量。为了执行这个可执行模块,该文件是必不可少的。5)如果链接程序发现D L L的源代码模块至少输出了一个函数或变量,那么链接程序也生成一个.l i b文件。这个.l i b文件很小,因为它不包含任何函数或变量。它只是列出所有已输出函数和变量的符号名。为了创建可执行模块,该文件是必不可少的。一旦创建了D L L模块,就可以创建可执行模块。其创建步骤是:6)在引用函数、变量、数据、结构或符号的所有源代码模块中,必须包含 D L L开发人员创建的头文件。7)要创建一个C/C+源代码模块(或多个模块),用于实现你想要在可执行模块中实现的函数和变量。当然该代码可以引用D L L
18、头文件中定义的函数和变量。8)创建可执行模块,将使编译器对每个源代码模块进行处理,生成一个.o b j模块(每个源代码模块有一个.o b j模块)。9)当所有.o b j模块创建完成后,链接程序便将所有的.o b j模块的内容组合起来,生成一个可执行的映像文件。该映像文件(或模块)包含了可执行文件的所有二进制代码和全局/静态变量。该可执行模块还包含一个输入节,列出可执行文件需要的所有 D L L模块名(关于各个节的详细说明,参见第1 7章)。此外,对于列出的每个D L L名字,该节指明了可执行模块的二进制代码引用了哪些函数和变量符号。下面你会看到操作系统的加载程序将对该输入节进行分析。一旦D
19、L L和可执行模块创建完成,一个进程就可以执行。当试图运行可执行模块时,操作系统的加载程序将执行下面的操作步骤:10)加载程序为新进程创建一个虚拟地址空间。可执行模块被映射到新进程的地址空间。加载程序对可执行模块的输入节进行分析。对于该节中列出的每个 D L L名字,加载程序要找出用户系统上的D L L模块,再将该D L L映射到进程的地址空间。注意,由于D L L模块可以从另一个D L L模块输入函数和变量,因此D L L模块可以拥有它自己的输入节。若要对进程进行全面的初始化,加载程序要分析每个模块的输入节,并将所有需要的D L L模块映射到进程的地址空间。如你所见,对进程进行初始化是很费时
20、间的。一旦可执行模块和所有D L L模块被映射到进程的地址空间中,进程的主线程就可以启动运行,同时应用程序也可以启动运行。下面各节将更加详细地介绍这个进程的运行情况。19.3 创建D L L模块当创建DLL 时,要创建一组可执行模块(或其他D L L)可以调用的函数。D L L可以将变量、函数或C/C+类输出到其他模块。在实际工作环境中,应该避免输出变量,因为这会删除你的代码中的一个抽象层,使它更加难以维护你的 D L L代码。此外,只有当使用同一个供应商提供的编译器对输入C+类的模块进行编译时,才能输出 C+类。由于这个原因,也应该避免输出C+类,除非知道可执行模块的开发人员使用的工具与D
21、L L模块开发人员使用的工具相同。当创建D L L模块时,首先应该建立一个头文件,该文件包含了你想要输出的变量(类型和名字)和函数(原型和名字)。头文件还必须定义用于输出函数和变量的任何符号和数据结构。你的D L L的所有源代码模块都应该包含这个头文件。另外,必须分配该头文件,以便它能够包第19章 DLL 基础计计467下载含在可能输入这些函数或变量的任何源代码中。拥有单个头文件,供 D L L创建程序和可执行模块的创建程序使用,就可以大大简化维护工作。下面的代码说明了应该如何对单个头文件进行编码,以便同时包含可执行文件和 D L L的源代码文件:在你的每个D L L源代码文件中,应该包含下面
22、的头文件:468计计第四部分动态链接库下载当上面的D L L源代码文件被编译时,在M y L i b.h头文件的前面使用_ _ d e c l s p e c(d l l e x p o r t)对M Y L I B A P I进行定义。当编译器看到负责修改变量、函数或 C+类的_ _ d e c l s p e c(d l l e x p o r t)时,它就知道该变量、函数或C+类是从产生的D L L模块输出的。注意,M Y L I B A P I标志被置于头文件中要输出的变量的定义之前和要输出的函数之前。另外,在源代码文件(M y L i b F i l e 1.c p p 0)中,MY
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Windows 核心 编程 019
限制150内