Windows核心编程020.pdf
《Windows核心编程020.pdf》由会员分享,可在线阅读,更多相关《Windows核心编程020.pdf(32页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、下载第2 0章D L L的高级操作技术上一章介绍了D L L链接的基本方法,并且重点说明了隐含链接的技术,这是 D L L链接的最常用的形式。虽然对于大多数应用程序来说,只要了解上一章介绍的知识就足够了,但是还可以使用D L L进行更多的工作。本章将要介绍与D L L相关的各种操作方法。大多数应用程序不一定需要这些方法,但是它们是非常有用的,所以应该对它们有所了解。20.1 DLL模块的显式加载和符号链接如果线程需要调用D L L模块中的函数,那么D L L的文件映像必须映射到调用线程的进程地创造DLL:1)建立带有输出原型/结构/符号的头文件。2)建立实现输出函数/变量的 C/C+源文件。3
2、)编译器为每个 C/C+源文件生成.obj模块。4)链接程序将生成DLL的.obj模块链接起来。5)如果至少输出一个函数/变量,那么链接程序也生成.lib 文件。创造EXE:6)建立带有输入原型/结构/符号的头文件(视情况而定)。7)建立不引用输入函数/变量的 C/C+源文件。8)编译器为每个 C/C+源文件生成.obj源文件。9)链接程序将各个.obj模块链接起来,生成.exe文件。注:DLL的lib文件是不需要的,因为并不直接引用输出符号。.exe 文件不包含输入表。运行应用程序:10)加载程序为.exe 创建模块地址空进程的主线程开始执行;应用程序启动运行。显式加载DLL:11)一个线程
3、调用LoadLibrary(Ex)函数,将DLL加载到进程的地址空间这时线程可以调用GetProcAddress以便间接引用DLL的输出符号。编译器编译器链接程序链接程序编译器编译器编译器编译器图20-1 应用程序创建和显式链接D L L的示意图址空间中。可以用两种方法进行这项操作。第一种方法是让应用程序的源代码只引用 D L L中包含的符号。这样,当应用程序启动运行时,加载程序就能够隐含加载(和链接)需要的 D L L。第二种方法是在应用程序运行时让应用程序显式加载需要的 D L L并且显式链接到需要的输出符号。换句话说,当应用程序运行时,它里面的线程能够决定它是否要调用 D L L中的函数
4、。该线程可以将D L L显式加载到进程的地址空间,获得D L L中包含的函数的虚拟内存地址,然后使用该内存地址调用该函数。这种方法的优点是一切操作都是在应用程序运行时进行的。图2 0-1显示了一个应用程序是如何显式地加载D L L并且链接到它里面的符号的。20.1.1 显式加载D L L模块无论何时,进程中的线程都可以决定将一个 D L L映射到进程的地址空间,方法是调用下面两个函数中的一个:这两个函数均用于找出用户系统上的文件映像(使用上一章中介绍的搜索算法),并设法将D L L的文件映像映射到调用进程的地址空间中。两个函数返回的 H I N S TA N C E值用于标识文件映像映射到的虚
5、拟内存地址。如果 D L L不能被映射到进程的地址空间,则返回 N U L L。若要了解关于错误的详细信息,可以调用G e t L a s t E r r o r.你会注意到,L o a d L i b r a r y E x函数配有两个辅助参数,即h F i l e和d w F l a g s。参数h F i l e保留供将来使用,现在必须是N U L L。对于参数 d w F l a g s,必须将它设置为 0,或者设置为D O N T _ R E S O LV E _ D L L _ R E F E R E N C E S、L O A D _ L I B R A RY _ A S _ D
6、 ATA F I L E和L O A D _ W I T H _A LT E R E D _ S E A R C H _ PAT H等标志的一个组合。1.DON T_RESOLV E _ D L L _ R E F E R E N C E SDON T_RESOLV E _ D L L _ R E F E R E N C E S标志用于告诉系统将D L L映射到调用进程的地址空间中。通常情况下,当D L L被映射到进程的地址空间中时,系统要调用D L L中的一个特殊函数,即 D l l M a i n(本章后面介绍)。该函数用于对 D L L进行初始化。DON T_RESOLV E _D L
7、L _ R E F E R E N C E S标志使系统不必调用D l l M a i n函数就能映射文件映像。此外,D L L能够输入另一个D L L中包含的函数。当系统将一个D L L映射到进程的地址空间中时,它也要查看该D L L是否需要其他的 D L L,并且自动加载这些D L L。当 D O NT _ R E S O LV E _ D L L _ R E F E R E N C E S标志被设定时,系统并不自动将其他的D L L加载到进程的地址空间中。2.LOAD_LIBRARY _ A S _ D ATA F I L EL O A D _ L I B R A RY _ A S _
8、D ATA F I L E标志与DON T_RESOLV E _ D L L _ R E F E R E N C E S标志相类似,因为系统只是将D L L映射到进程的地址空间中,就像它是数据文件一样。系统并不花费额外的时间来准备执行文件中的任何代码。例如,当一个 D L L被映射到进程的地址空间中时,系统要查看D L L中的某些信息,以确定应该将哪些页面保护属性赋予文件的不同的节。如果设定了L O A D _ L I B R A RY _ A S _ D ATA F I L E标志,系统将以它要执行文件中的代码时的同样方式来设置页面保护属性。由于下面几个原因,该标志是非常有用的。首先,如果有
9、一个 D L L(它只包含资源,但不478计计第四部分动态链接库下载包含函数),那么可以设定这个标志,使 D L L的文件映像能够映射到进程的地址空间中。然后可以在调用加载资源的函数时,使用 L o a d L i b r a r y E x函数返回的H I N S TA N C E值。通常情况下,加载一个.e x e文件,就能够启动一个新进程,但是也可以使用 L o a d L i b r a r y E x函数将.e x e文件的映像映射到进程的地址空间中。借助映射的.e x e文件的H I N S TA N C E值,就能够访问文件中的资源。由于.e x e文件没有D l l M a i
10、 n函数,因此,当调用L o a d L i b r a r y E x来加载一个.e x e文件时,必须设定L O A D _ L I B R A RY _ A S _ D ATA F I L E标志。3.LOAD_WITH_ALT E R E D _ S E A R C H _ PAT HL O A D _ W I T H _ A LT E R E D _ S E A R C H _ PAT H标志用于改变L o a d L i b r a r y E x用来查找特定的D L L文件时使用的搜索算法。通常情况下,L o a d L i b r a r y E x按照第1 9章讲述的顺序进行
11、文件的搜索。但是,如果设定了L O A D _ W I T H _ A LT E R E D _ S E A R C H _ PAT H标志,那么L o a d L i b r a r y E x函数就按照下面的顺序来搜索文件:1)pszDLLPathName参数中设定的目录。2)进程的当前目录。3)Wi n d o w s的系统目录。4)Wi n d o w s目录。5)PAT H环境变量中列出的目录。20.1.2 显式卸载D L L模块当进程中的线程不再需要 D L L中的引用符号时,可以从进程的地址空间中显式卸载 D L L,方法是调用下面的函数:必须传递 H I N S TA N C
12、E 值,以便标识要卸载的D L L。该值是较早的时候调用L o a d L i b r a r y(E x)而返回的值。也可以通过调用下面的函数从进程的地址空间中卸载D L L:该函数是在K e r n e l 3 2.d l l中实现的,如下所示:初看起来,这并不是个非常高明的代码,你可能不明白,为什么M i c r o s o f t要创建F r e e L i b r a r y A n d E x i t T h r e a d这个函数。其原因与下面的情况有关:假定你要编写一个 D L L,当它被初次映射到进程的地址空间中时,该D L L就创建一个线程。当该线程完成它的操作时,它通过调
13、用F r e e L i b r a r y函数,从进程的地址空间中卸载该 D L L,并且终止运行,然后立即调用E x i t T h r e a d。但是,如果线程分开调用F r e e L i b r a r y和E x i t T h r e a d,就会出现一个严重的问题。这个问题是调用F r e e L i b r a r y会立即从进程的地址空间中卸载D L L。当调用的F r e e L i b r a r y返回时,包含对E x i t T h r e a d调用的代码就不再可以使用,因此线程将无法执行任何代码。这将导致访问违规,同时整个进程终止运行。但是,如果线程调用F r
14、 e e L i b r a r y A n d E x i t T h r e a d,该函数调用F r e e L i b r a r y,使D L L立即被卸第20章 DLL的高级操作技术计计479下载载。下一个执行的指令是在K e r n e l 3 2.d l l中,而不是在刚刚被卸载的D L L中。这意味着该线程能够继续执行,并且可以调用E x i t T h r e a d。E x i t T h r e a d使该线程终止运行并且不返回。一般来说,并没有很大的必要去调用 F r e e L i b r a r y A n d E x i t T h r e a d函数。我曾经使
15、用过一次,因为我执行了一个非常特殊的任务。另外,我为 Microsoft Windows 3.1编写了一个代码,它并没有提供这个函数。因此我高兴地看到M i c r o s o f t将这个函数增加到了较新的Wi n d o w s版本中。在实际环境中,L o a d L i b r a r y和L o a d L i b r a r y E x这两个函数用于对与特定的库相关的进程使用计数进行递增,F r e e L i b r a r y和F r e e L i b r a r y A n d E x i t T h r e a d这两个函数则用于对库的每个进程的使用计数进行递减。例如,当第
16、一次调用L o a d L i b r a r y函数来加载D L L时,系统将D L L的文件映像映射到调用进程的地址空间中,并将 D L L的使用计数设置为1。如果同一个进程中的线程后来调用L o a d L i b r a r y来加载同一个D L L文件映像,系统并不第二次将D L L映像文件映射到进程的地址空间中,它只是将与该进程的D L L相关的使用计数递增1。为了从进程的地址空间中卸载D L L文件映像,进程中的线程必须两次调用F r e e L i b r a r y函数。第一次调用只是将D L L的使用计数递减为1,第二次调用则将D L L的使用计数递减为0。当系统发现D L
17、 L的使用计数递减为 0时,它就从进程的地址空间中卸载 D L L的文件映像。试图调用D L L中的函数的任何线程都会产生访问违规,因为特定地址上的代码不再被映射到进程的地址空间中。系统为每个进程维护了一个D L L的使用计数,也就是说,如果进程 A中的一个线程调用下面的函数,然后进程B中的一个线程调用相同的函数,那么M y L i b.d l l将被映射到两个进程的地址空间中,这样,进程A和进程B的D L L使用计数都将是1。如果进程B中的线程后来调用下面的函数,那么进程 B的D L L使用计数将变成 0,并且该D L L将从进程B的地址空间中卸载。但是,进程 A的地址空间中的D L L映射
18、不会受到影响,进程A的D L L使用计数仍然是1。如果调用G e t M o d u l e H a n d l e函数,线程就能够确定D L L是否已经被映射到进程的地址空间中:例如,只有当MyLib.dll尚未被映射到进程的地址空间中时,下面这个代码才能加载该文件:如果只有 D L L的H I N S TA N C E值,那么可以调用 G e t M o d u l e F i l e N a m e函数,确定 D L L(或.e x e)的全路径名:第一个参数是D L L(或.e x e)的H I N S TA N C E。第二个参数p s z P a t h N a m e是该函数将文
19、件映像的全路径名放入的缓存的地址。第三参数c c h P a t h用于设定缓存的大小(以字符为计量单位)。20.1.3 显式链接到一个输出符号一旦D L L模块被显式加载,线程就必须获取它要引用的符号的地址,方法是调用下面的函数:480计计第四部分动态链接库下载参数h i n s t D l l是调用L o a d L i b r a r y(E x)或G e t M o d u l e H a n d l e函数而返回的,它用于设定包含符号的D L L的句柄。参数p s z S y m b o l N a m e可以采用两种形式。第一种形式是以 0结尾的字符串的地址,它包含了你想要其地址的
20、符号的名字:注意,参数p s z S y m b o l N a m e的原型是P C S T R,而不是P C T S T R。这意味着G e t P r o c A d d r e s s函数只接受A N S I字符串,决不能将U n i c o d e字符串传递给该函数,因为编译器/链接程序总是将符号名作为A N S I字符串存储在D L L的输出节中。参数p s z S y m b o l N a m e的第二种形式用于指明你想要其地址的符号的序号:这种用法假设你知道你需要的符号名被 D L L创建程序赋予了序号值2。同样,我要再次强调,M i c r o s o f t非常反对使用序
21、号,因此你不会经常看到G e t P r o c A d d r e s s的这个用法。这两种方法都能够提供包含在D L L中的必要符号的地址。如果D L L模块的输出节中不存在你需要的符号,G e t P r o c A d d r e s s就返回N U L L,表示运行失败。应该知道,调用G e t P r o c A d d r e s s的第一种方法比第二种方法要慢,因为系统必须进行字符串的比较,并且要搜索传递的符号名字符串。对于第二种方法来说,如果传递的序号尚未被分配给任何输出的函数,那么 G e t P r o c A d d r e s s就会返回一个非N U L L值。这个返
22、回值将会使你的应用程序错误地认为你已经拥有一个有效的地址,而实际上你并不拥有这样的地址。如果试图调用该地址,肯定会导致线程引发一个访问违规。我在早期从事 Wi n d o w s编程时,并不完全理解这个行为特性,因此多次出现这样的错误。所以一定要小心(这个行为特性是应该避免使用序号而使用符号名的另一个原因)。20.2 DLL的进入点函数一个D L L可以拥有单个进入点函数。系统在不同的时间调用这个进入点函数,这个问题将在下面加以介绍。这些调用可以用来提供一些信息,通常用于供 D L L进行每个进程或线程的初始化和清除操作。如果你的 D L L不需要这些通知信息,就不必在 D L L源代码中实现
23、这个函数。例如,如果你创建一个只包含资源的D L L,就不必实现该函数。如果确实需要在D L L中接受通知信息,可以实现类似下面的进入点函数:第20章 DLL的高级操作技术计计481下载注意函数名D l l M a i n是区分大小写的。许多编程人员有时调用的函数是 D L L M a i n。这是一个非常容易犯的错误,因为D L L这个词常常使用大写来表示。如果调用的进入点函数不是D l l M a i n,而是别的函数,你的代码将能够编译和链接,但是你的进入点函数永远不会被调用,你的D L L永远不会被初始化。参数h i n s t D l l包含了D L L的实例句柄。与(w)Wi n
24、M a i n函数的h i n s t E x e参数一样,这个值用于标识D L L的文件映像被映射到进程的地址空间中的虚拟内存地址。通常应将这个参数保存在一个全局变量中,这样就可以在调用加载资源的函数(如 D i a l o g B o x和L o a d S t r i n g)时使用它。最后一个参数是f I m p L o a d,如果D L L是隐含加载的,那么该参数将是个非 0值,如果D L L是显式加载的,那么它的值是0。参数f d w R e a s o n用于指明系统为什么调用该函数。该参数可以使用 4个值中的一个。这4个值是:D L L _ P R O C E S S _ A
25、T TA C H、D L L _ P R O C E S S _ D E TA C H、D L L _ T H R E A D _ AT TA C H或D L L _ T H R E A D _ D E TA C H。这些值将在下面介绍。注意 必须记住,D L L使用D l l M a i n函数来对它们进行初始化。当你的D l l M a i n函数执行时,同一个地址空间中的其他D L L可能尚未执行它们的D l l M a i n函数。这意味着它们尚未初始化,因此你应该避免调用从其他D L L中输入的函数。此外,你应该避免从D l l M a i n内部调用L o a d L i b r
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Windows 核心 编程 020
限制150内