欢迎来到淘文阁 - 分享文档赚钱的网站! | 帮助中心 好文档才是您的得力助手!
淘文阁 - 分享文档赚钱的网站
全部分类
  • 研究报告>
  • 管理文献>
  • 标准材料>
  • 技术资料>
  • 教育专区>
  • 应用文书>
  • 生活休闲>
  • 考试试题>
  • pptx模板>
  • 工商注册>
  • 期刊短文>
  • 图片设计>
  • ImageVerifierCode 换一换

    windows核心编程指南20.pdf

    • 资源ID:70322074       资源大小:2.30MB        全文页数:32页
    • 资源格式: PDF        下载积分:15金币
    快捷下载 游客一键下载
    会员登录下载
    微信登录下载
    三方登录下载: 微信开放平台登录   QQ登录  
    二维码
    微信扫一扫登录
    下载资源需要15金币
    邮箱/手机:
    温馨提示:
    快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。
    如填写123,账号就是123,密码也是123。
    支付方式: 支付宝    微信支付   
    验证码:   换一换

     
    账号:
    密码:
    验证码:   换一换
      忘记密码?
        
    友情提示
    2、PDF文件下载后,可能会被浏览器默认打开,此种情况可以点击浏览器菜单,保存网页到桌面,就可以正常下载了。
    3、本站不支持迅雷下载,请使用电脑自带的IE浏览器,或者360浏览器、谷歌浏览器下载即可。
    4、本站资源下载后的文档和图纸-无水印,预览文档经过压缩,下载后原文更清晰。
    5、试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。

    windows核心编程指南20.pdf

    下载第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)编译器为每个 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)一个线程调用LoadLibrary(Ex)函数,将DLL加载到进程的地址空间这时线程可以调用GetProcAddress以便间接引用DLL的输出符号。编译器编译器链接程序链接程序编译器编译器编译器编译器图20-1 应用程序创建和显式链接D L L的示意图址空间中。可以用两种方法进行这项操作。第一种方法是让应用程序的源代码只引用 D L L中包含的符号。这样,当应用程序启动运行时,加载程序就能够隐含加载(和链接)需要的 D L L。第二种方法是在应用程序运行时让应用程序显式加载需要的 D L L并且显式链接到需要的输出符号。换句话说,当应用程序运行时,它里面的线程能够决定它是否要调用 D L L中的函数。该线程可以将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值用于标识文件映像映射到的虚拟内存地址。如果 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 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 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 _ 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标志,系统将以它要执行文件中的代码时的同样方式来设置页面保护属性。由于下面几个原因,该标志是非常有用的。首先,如果有一个 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 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章讲述的顺序进行文件的搜索。但是,如果设定了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 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就创建一个线程。当该线程完成它的操作时,它通过调用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 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函数。我曾经使用过一次,因为我执行了一个非常特殊的任务。另外,我为 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这两个函数则用于对库的每个进程的使用计数进行递减。例如,当第一次调用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 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映射不会受到影响,进程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是该函数将文件映像的全路径名放入的缓存的地址。第三参数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结尾的字符串的地址,它包含了你想要其地址的符号的名字:注意,参数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非常反对使用序号,因此你不会经常看到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值。这个返回值将会使你的应用程序错误地认为你已经拥有一个有效的地址,而实际上你并不拥有这样的地址。如果试图调用该地址,肯定会导致线程引发一个访问违规。我在早期从事 Wi n d o w s编程时,并不完全理解这个行为特性,因此多次出现这样的错误。所以一定要小心(这个行为特性是应该避免使用序号而使用符号名的另一个原因)。20.2 DLL的进入点函数一个D L L可以拥有单个进入点函数。系统在不同的时间调用这个进入点函数,这个问题将在下面加以介绍。这些调用可以用来提供一些信息,通常用于供 D L L进行每个进程或线程的初始化和清除操作。如果你的 D L L不需要这些通知信息,就不必在 D L L源代码中实现这个函数。例如,如果你创建一个只包含资源的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 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 _ AT 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 a r y(E x)和F r e e L i b r a r y函数,因为这些函数会形式一个依赖性循环。Platform SDK文档说,你的D l l M a i n函数只应该进行一些简单的初始化,比如设置本地存储器(第 2 1章介绍),创建内核对象和打开文件等。你还必须避免调用 U s e r、S h e l l、O D B C、C O M、R P C和套接字函数(即调用这些函数的函数),因为它们的D L L也许尚未初始化,或者这些函数可能在内部调用L o a d L i b r a r y(E x)函数,这同样会形成一个依赖性循环。另外,如果创建全局性的或静态的C+对象,那么应该注意可能存在同样的问题,因为在你调用D l l M a i n函数的同时,这些对象的构造函数和析构函数也会被调用。20.2.1 DLL_PROCESS_AT TA C H通知当D L L被初次映射到进程的地址空间中时,系统将调用该 D L L的D l l M a i n函数,给它传递参数f d w R e a s o n的值D L L _ P R O C E S S _ AT TA C H。只有当D L L的文件映像初次被映射时,才会出现这种情况。如果线程在后来为已经映射到进程的地址空间中的D L L调用L o a d L i b r a r y(E x)函数,那么操作系统只是递增 D L L的使用计数,它并不再次用 D L L _ P R O C E S S _ AT TA C H的值来调用D L L的D l l M a i n函数。当处理D L L _ P R O C E S S _ AT TA C H时,D L L应该执行D L L中的函数要求的任何与进程相关的初始化。例如,D L L可能包含需要使用它们自己的堆栈(在进程的地址空间中创建)的函数。通过在处理D L L _ P R O C E S S _ AT TA C H通知时调用H e a p C r e a t e函数,该D L L的D l l M a i n函数就能够创建这个堆栈。已经创建的堆栈的句柄可以保存在D L L函数有权访问的一个全局变量中。当D l l M a i n处理一个D L L _ P R O C E S S _ AT TA C H通知时,D l l M a i n的返回值能够指明D L L的初482计计第四部分动态链接库下载始化是否已经取得成功。如果对H e a p C r e a t e的调用取得了成功,D l l M a i n应该返回T R U E。如果堆栈不能创建,它应该返回FA L S E。如果 f d w R e a s o n使用的是其他的值,即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 M a i n返回的值。当然,系统中的有些线程必须负责执行 D l l M a i n函数中的代码。当一个新线程创建时,系统将分配进程的地址空间,然后将.e x e文件映像和所有需要的D L L文件映像映射到进程的地址空间中。然后它创建进程的主线程,并使用该线程调用每个D L L的带有D L L _ P R O C E S S _AT TACH 值的D l l M a i n函数。当已经映射的所有D L L都对通知信息作出响应后,系统将使进程的主线程开始执行可执行模块的 C/C+运行期启动代码,然后执行可执行模块的进入点函数(m a i n、w m a i n、Wi n M a i n或w Wi n M a i n)。如果D L L的任何一个D l l M a i n函数返回FA L S E,指明初始化没有取得成功,系统便终止整个进程的运行,从它的地址空间中删除所有文件映像,给用户显示一个消息框,说明进程无法启动运行。Windows 2000的这个消息框如 图2 0-2所示,再下面是Windows 98的消息框(见图2 0-3)。下面让我们来看一看D L L被显式加载时的情况。当进程中的一个线程调用 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)的线程,调用D L L的带有D L L _ P R O C E S S _ AT TACH 值的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 a r y(E x)函数返回,同时该线程像平常一样继续进行处理。如果 D l l M a i n函数返回FA L S E,指明初始化没有取得成功,那么系统就自动从进程的地址空间中卸载D L L的文件映像,而对L o a d L i b r a r y(E x)的调用则返回N U L L。20.2.2 DLL_PROCESS_DETA C H通知D L L从进程的地址空间中被卸载时,系统将调用 D L L的D l l M a i n函数,给它传递f d w R e a s o n的值D L L _ P R O C E S S _ D E TA C H。当D L L处理这个值时,它应该执行任何与进程相关的清除操作。例如,D L L可以调用H e a p D e s t r o y函数来撤消它在D L L _ P R O C E S S _ D E TA C H通知期间创建的堆栈。注意,如果 D l l M a i n函数接收到D L L _ P R O C E S S _ D E TA C H通知时返回FA L S E,那么D l l M a i n就不是用D L L _ P R O C E S S _ D E TA C H通知调用的。如果因为进程终止运行而使 D L L被卸载,那么调用E x i t P r o c e s s函数的线程将负责执行D l l M a i n函数的代码。在正常情况下,这是应用程序的主线程。当你的进入点函数返回到 C/C+运行期库的启动代码时,该启动代码将显式调用E x i t P r o c e s s函数,终止进程的运行。如果因为进程中的线程调用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函数而将D L L卸载,那么调用函数的线程将负责执行D l l M a i n函数的代码。如果使用F r e e L i b r a r y,那么要等到D l l M a i n函数完成对D L L _ P R O C E S S _ D E TA C H通知的执行后,该线程才从对 F r e e L i b r a r y函数的调用中返回。注意,D L L能够阻止进程终止运行。例如,当 D l l M a i n接收到D L L _ P R O C E S S _ D E TA C H通知时,它就会进入一个无限循环。只有当每个 D L L都已完成对D L L _ P R O C E S S _ D E TA C H通知第20章 DLL的高级操作技术计计483图20-2 Windows 2000下显示的消息框图20-3 Windows 98下显示的消息框下载的处理时,操作系统才会终止该进程的运行。注意如果因为系统中的某个线程调用了Te r m i n a t e P r o c e s s而使进程终止运行,那么系统将不调用带有D L L _ P R O C E S S _ D E TA C H值的 D L L的D l l M a i n函数。这意味着映射到进程的地址空间中的任何D L L都没有机会在进程终止运行之前执行任何清除操作。这可能导致数据的丢失。只有在迫不得已的情况下,才能使用 Te r m i n a t e P r o c e s s函数。图2 0-4显示了线程调用L o a d L i b r a r y时执行的操作步骤。图2 0-5显示了线程调用F r e e L i b r a r y函数时执行的操作步骤。图20-4 线程调用L o a d L i b r a r y时系统执行的操作步骤484计计第四部分动态链接库下载线程调用Load Library 函数D LL 已经映射到进程的地址空间中了吗?是是是否是否否否系统能够找到指定的D L L文件吗?使用计数等于1吗?返回该库的hinstDLL (加载地址)返回N U L LDllMain 函数返回TRUE 了吗?递增D LL的使用计数将D LL映射到进程的地址空间中调用该库带有DLL_PROCESS_ATTACH值的Dll Main函数递减D LL的使用计数,从进程的地址空间中撤消D LL图20-5 线程调用F r e e L i b r a r y时系统执行的操作步骤20.2.3 DLL_THREAD_AT TA C H通知当在一个进程中创建线程时,系统要查看当前映射到该进程的地址空间中的所有 D L L文件映像,并调用每个文件映像的带有 D L L _ T H R E A D _ AT TA C H值的D l l M a i n函数。这可以告诉所有的D L L执行每个线程的初始化操作。新创建的线程负责执行 D L L的所有D l l M a i n函数中的代码。只有当所有的D L L都有机会处理该通知时,系统才允许新线程开始执行它的线程函数。当一个新D L L被映射到进程的地址空间中时,如果该进程内已经有若干个线程正在运行,那么系统将不为现有的线程调用带有D L L _ T H R E A D _ AT TA C H值的DDL 的D l l M a i n函数。只有当新线程创建时D L L被映射到进程的地址空间中,它才调用带有D L L _ T H R E A D _ AT TA C H值的D L L的D l l M a i n函数。另外要注意,系统并不为进程的主线程调用带有D L L _ T H R E A D _ AT TA C H值的任何D l l M a i n函数。进程初次启动时映射到进程的地址空间中的任何D L L均接收 D L L _P R O C E S S _ AT TA C H通知,而不是D L L _ T H R E A D _ AT TA C H通知。20.2.4 DLL_THREAD_DETA C H通知让线程终止运行的首选方法是使它的线程函数返回。这使得系统可以调用 E x i t T h r e a d来撤第20章 DLL的高级操作技术计计485线程调用F r e eL i b r a r y函数hinstDll 参数有效吗?递减DLL 的使用计数使用计数等于0吗?返回T R U E返回FA S E调用该库带有D L L _P R O C ES S _ D E TACH 值的Dl l M a i n函数从进程的地址空间中撤消该D L L否否是是下载消该线程。E x i t T h r e a d函数告诉系统,该线程想要终止运行,但是系统并不立即将它撤消。相反,它 要 取 出 这 个 即 将 被 撤 消 的 线 程,并 让 它 调 用 已 经 映 射 的 D L L 的 所 有 带 有D L L _ T H R E A D _ D E TACH 值的D l l M a i n函数。这个通知告诉所有的D L L执行每个线程的清除操作。例如,D L L版本的C/C+运行期库能够释放它用于管理多线程应用程序的数据块。注意,D L L能够防止线程终止运行。例如,当D l l M a i n函数接收到 D L L _ T H R E A D _D E TA C H通知时,它就能够进入一个无限循环。只有当每个 D L L已经完成对D L L _ T H R E A D _D E TA C H通知的处理时,操作系统才会终止线程的运行。注意如果因为系统中的线程调用Te r m i n a t e T h r e a d函数而使该线程终止运行,那么系统将不调用带有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都没有机会在线程终止运行之前执行任何清除操作。这可能导致数据的丢失。与Te r m i n a t e P r o c e s s一样,只有在迫不得已的时候,才可以使用Te r m i n a t e T h r e a d函数。如果当D L L被撤消时仍然有线程在运行,那么就不为任何线程调用带有 D L L _ T H R E A D _D E TA C H值的D l l M a i n。可以在进行D L L _ T H R E A D _ D E TA C H的处理时查看这个情况,这样就能够执行必要的清除操作。上述规则可能导致发生下面这种情况。当进程中的一个线程调用L o a d L i b r a r y来加载D L L时,系统就会调用带有D L L _ P R O C E S S _ AT TA C H值的D L L的D l l M a i n函数(注意,没有为该线程发送D L L _ T H R E A D _ AT TA C H通知)。接着,负责加载D L L的线程退出,从而导致D L L的D l l M a i n函数被再次调用,这次调用时带有D L L _ T H R E A D _ D E TA C H值。注意,D L L得到通知说,该线程将被撤消,尽管它从未收到D L L _ T H R E A D _ AT TA C H的这个通知,这个通知告诉该库说线程已经附加。由于这个原因,当执行任何特定的线程清除操作时,必须非常小心。不过大多数程序在编写时就规定调用L o a d L i b r a r y的线程与调用F r e e L i b r a r y的线程是同一个线程。20.2.5 顺序调用D l l M a i n系统是顺序调用D L L的D l l M a i n函数的。为了理解这样做的意义,可以考虑下面这样一个环境。假设一个进程有两个线程,线程 A和线程B。该进程还有一个D L L,称为S o m e D L L.d l l,它被映射到了它的地址空间中。两个线程都准备调用 C r e a t e T h r e a d函数,以便再创建两个线程,即线程C和线程D。当线程A调用C r e a t e T h r e a d来创建线程C时,系统调用带有 D L L _ T H R E A D _ AT TA C H值的S o m e D L L.d l l的D l l M a i n函数。当线程C执行D l l M a i n函数中的代码时,线程B调用C r e a t e T h r e a d函数来创建线程D。这时系统必须再次调用带有D L L _ T H R E A D _ AT TA C H值的D l l M a i n函数,这次是让线程D 执行代码。但是,系统是顺序调用 D l l M a i n函数的,因此系统会暂停线程 D的运行,直到线程C完成对D l l M a i n函数中的代码的处理并且返回为止。当线程C完成D l l M a i n的处理后,它就开始执行它的线程函数。这时系统唤醒线程 D,让它处理D l l M a i n中的代码。当它返回时,线程D开始处理它的线程函数。通常情况下,根本不会考虑到 D l l M a i n的这个顺序操作特性。我曾经遇到过一个人,他的代码中有一个D l l M a i n顺序操作带来的错误。他创建的代码类似下面的样子:486计计第四部分动态链接库下载我们花了好几个小时才发现这个代码中存在的问题。你能够看出这个问题吗?当 D l l M a i n收 到 D L L _ P R O C E S S _ AT TA C H 通 知 时,一 个 新 线 程 就 创 建 了。系 统 必 须 用D L L _ T H R E A D _ AT TA C H的值再次调用 D l l M a i n函数。但是,新线程被暂停运行,因为导致D L L _ P R O C E S S _ AT TA C H被发送给 D l l M a i n函数的线程尚未完成处理操作。问题是调用Wa i t F o r S i n g l e O b j e c t函数而产生的。这个函数使当前正在运行的线程暂停运行,直到新线程终止运行。但是新线程从未得到机会运行,更不要说终止运行,因为它处于暂停状态,等待当前线程退出D l l M a i n函数。这里我们得到的是个死锁条件。两个线程将永远处于暂停状态。当我刚刚开始考虑如何解决这个问题的时候,我发现了D i s a b l e T h r e a d L i b r a r y C a l l s函数:D i s a b l e T h r e a d L i b r a r y C a l l s告诉系统说,你不想将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通知发送给D L L,那么就不会发送死锁条件。但是当我测试解决方案时,我很快发现它解决不了问题。请看下面的代码:第20章 DLL的高级操作技术计计487下载通过进一步的研究,我终于发现了问题。当进程被创建时,系统也创建一个互斥对象。每个进程都有它自己的互斥对象,也就是说多个进程并不共享互斥对象。当线程调用映射到进程的地址空间中的D L L的D l l M a i n函数时,这个互斥对象负责对进程的所有线程实施同步

    注意事项

    本文(windows核心编程指南20.pdf)为本站会员(asd****56)主动上传,淘文阁 - 分享文档赚钱的网站仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知淘文阁 - 分享文档赚钱的网站(点击联系客服),我们立即给予删除!

    温馨提示:如果因为网速或其他原因下载失败请重新下载,重复下载不扣分。




    关于淘文阁 - 版权申诉 - 用户使用规则 - 积分规则 - 联系我们

    本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知淘文阁网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

    工信部备案号:黑ICP备15003705号 © 2020-2023 www.taowenge.com 淘文阁 

    收起
    展开