友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
第三电子书 返回本书目录 加入书签 我的书架 我的书签 TXT全本下载 『收藏到我的浏览器』

VC语言6.0程序设计从入门到精通-第52部分

快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!


外需要注意现在大多数用户使用的是单 CPU 计算机,在这种机器上运行多线程程序,有时反 

而会降低系统的性能 。因此,在设计多线程应用程序时,应慎重选择,视具体情况加以处理, 

使应用程序获得最佳的性能。  



 ·250 ·  


…………………………………………………………Page 262……………………………………………………………

              第 10 章    动态链接库  

              第 10 章    动态链接库  



   动态链接库(Dynamic Link Library )是一个可执行模块,其包含的函数可以由 Windows 

应用程序调用以执行一些功能,主要为应用程序模块提供服务。本章将全面、系统地阐述在 

Visual C++平台下进行 Win32 动态链接库的设计和应用。主要包括下面几个方面的内容。  

   o  动态链接库(DLL )的基本知识。  

   o  DLL 的出入口函数。  

   o  调用 DLL 中的两种方式。  

   o  开发 DLL 的方式。  

   o  DLL 中资源的利用。  

   o  钩子(Hook )函数的应用方法。  

   为了使读者充分理解概念,对于动态链接库的开发,本章使用了“界面汉化”的示例来 

说明资源在动态链接库中的使用 。钩子函数对于大多数读者来说可能是一项较为陌生的技术, 

为了加深理解,这里列举了两个关于捕获消息的钩子函数示例,可以帮助读者更好地理解钩 

子函数的原理和使用方法。  



10。1    动态链接库的基础知识  



   比较大的应用程序都是由很多模块组成的,这些模块彼此协作,以完成整个软件系统的 

工作。其中可能存在一些模块的功能较为通用,在构造其他软件系统时仍会被使用。在构造 

软件系统时,如果将所有模块的源代码都静态编译到整个应用程序 EXE 文件中,会产生一些 

问题。一是增加了应用程序的大小,这样会占用更多的磁盘空间,程序运行时也会消耗较大 

的内存空间,造成系统资源的浪费;另外,在编写大的 EXE 程序时,每次修改重建时都必须 

调整编译所有源代码,不但增加了编译过程的复杂性,也不利于阶段性的单元测试。  

   Windows 系统平台上提供了一种完全不同的有效编程和运行环境,可以将独立的程序模 

块创建为较小的动态链接库(Dynamic  Linkable  Library )文件,并可对它们单独进行编译和 

测试。在运行时,只有在 EXE 程序确实要调用这些 DLL 模块的情况下,系统才会将它们装 

载到内存空间中。这种方式不仅减少了  EXE     文件的大小和对内存空间的需求,而且使这些 

DLL 模块可以同时被多个应用程序使用,从而充分利用资源。Microsoft Windows 将一些主要 

的系统功能以 DLL 模块的形式实现 。例如 IE 中的一些基本功能就是由 DLL 文件实现的,它 

可以被其他应用程序调用和集成。一般来说,下面的这几种情况必须用到动态链接库技术。  

   o  多个应用程序共享代码和数据就是通过共享动态链接库实现的,比如 Office 软件的各 

    个组成部分有相似的外观和功能。  

   o  在钩子程序过滤系统消息时必须使用动态链接库。  

   o  设备驱动程序必须是动态链接库。  


…………………………………………………………Page 263……………………………………………………………

Visual C++ 6。0 程序设计从入门到精通  



   o  如果要在对话框编辑器中使用自己定义的控件,也必须使用动态链接库。  

   o  动态链接库以一种自然的方式将一个大的应用程序划分为几个小的模块,有利于小组 

     内部成员的分工与合作。而且,各个模块可以独立升级。如果小组中的一个成员开发 

    了一组实用示例,他就可以把这些示例放在一个动态链接库中,让小组的其他成员使 

    用。  

   o  为了实现应用程序的国际化,往往需要使用动态链接库。使用动态链接库可以将针对 

    某一国家、语言的信息存放在其中。对于不同的版本,使用不同的动态链接库。在使 

    用 AppWizard 生成应用程序时,可以指定资源文件使用的语言,这就是通过提供不同 

    的动态链接库实现的。  

   一般来说,DLL 是一种磁盘文件(通常带有 DLL 扩展名),它由全局数据、服务函数和 

资源组成,在运行时被系统加载到进程的虚拟空间中,成为调用进程的一部分。如果与其他 

DLL 之间没有冲突,该文件通常映射到进程虚拟空间的同一地址上。DLL 模块中包含各种导 

出函数,用于向外界提供服务。Windows 在加载 DLL 模块时将进程函数调用与 DLL 文件的 

导出函数相匹配。  

   在 Win32 环境中,每个进程都复制了自己的读写全局变量 。如果想要与其他进程共享内 

存,必须使用内存映射文件或者声明一个共享数据段。DLL 模块需要的堆栈内存都是从运行 

进程的堆栈中分配出来的。DLL 现在越来越容易编写。Win32  已经大大简化了其编程模式, 

并有许多来自 AppWizard 和 MFC 类库的支持。使用 Visual C++ 6。0 工具可以编写 3 种不同类 

型的动态链接库。  

   o  Non…MFC DLL :指的是不用 MFC 的类库结构,直接用 C 语言编写的 DLL ,其输出的 

    函数一般用的是标准 C 接口,并能被非 MFC 或 MFC 编写的应用程序所调用。  

   o  Regular DLL :和下述的 Extension Dlls 一样,是用 MFC 类库编写的。其特点是在源文 

    件里有一个继承 CWinApp  的类。其又可细分成静态连接到 MFC 和动态连接到 MFC 

    上的。但静态连接到 MFC 的动态链接库只被 Visual C++ 的专业版和企业版所支持。  

   o  Extension  DLL :用来实现从 MFC 所继承下来的类的重新利用,也就是说,用这种类 

    型的动态链接库,可以用来输出一个从  MFC       所继承下来的类。Extension  DLL 使用 

    MFC 的动态连接版本所创建的,并且它只被用 MFC 类库所编写的应用程序所调用。  



10。2    DLL 的出入口函数  



   DllMain() 函数是 DLL 模块的默认入口点。当 Windows 加载 DLL 模块时调用这一函数。 

系统首先调用全局对象的构造函数,然后调用全局函数  DllMain() 。DllMain() 函数不仅在将 

DLL 链接加载到进程时被调用,在 DLL 模块与进程分离时(以及其他时候)也被调用。  

   DLL 文件中包含一个导出函数表 。这些导出函数由它们的符号名和称为标识号的整数与 

外界联系起来。函数表中还包含了 DLL  中函数的地址。当应用程序加载 DLL 模块时,它并 

不知道调用函数的实际地址,但它知道函数的符号名和标识号。动态链接过程在加载  DLL 

模块时动态建立一个函数调用与函数地址的对应表 。如果重新编译和重建 DLL 文件,并不需 

要修改应用程序,除非改变了导出函数的符号名和参数序列。  

   简单的 DLL 文件只为应用程序提供导出函数,比较复杂的 DLL 文件除了提供导出函数 



·252 ·  


…………………………………………………………Page 264……………………………………………………………

                                                                                 第 10 章    动态链接库  



以外,还调用其他 DLL 文件中的函数。这样,一个特殊的 DLL 既有导入函数,又有导出函 

数。这并不是一个问题,因为动态链接过程可以处理交叉相关的情况。  

     在 DLL 代码中,声明导出函数的代码如下:  



     __declspec(dllexport) int MyFunction(int n);  



     但也可以在模块定义(DEF)文件中列出导出函数,不过这样做常常引起更多的麻烦。在 

应用程序方面,声明相应的输入函数,代码如下:  



     __declspec(dllimport) int MyFuncition(int n);  



     仅有导入和导出声明并不能使应用程序内部的函数调用链接到相应的 DLL 文件上。应用 

程序的项目必须为链接程序指定所需的输入库(LIB 文件)。而且应用程序事实上必须至少包 

含一个对 DLL 函数的调用。本节将对 DLL 的 DllMain()入口函数和导出函数作相关的介绍。  



10。2。1    DllMain()函数  



     每一个 DLL 必须有一个入口点,这就象用 C 编写的应用程序一样,必须有一个 WinMain 

函数一样。在 Non…MFC  DLL  中 DllMain()是一个默认的入口函数,不需要编写自己的 DLL 

入口函数,用这个默认的入口函数就能使动态链接库在被调用时初始化。如果应用程序的 

DLL 需要分配额外的内存或资源,即对每个进程或线程初始化和清除操作时,就需要在相应 

的 DLL 工程的 CPP 文件中对 DllMain() 函数按照下面的格式书写,代码如下:  



     BOOL APIENTRY DllMain(HANDLE hModule;DWORD ul_reason_for_call;LPVOID lpReserved)  



     {  



          switch( ul_reason_for_call )  



          {  



          case DLL_PROCESS_ATTACH:  



               。。。。。。。  



          case DLL_THREAD_ATTACH:  



               。。。。。。。  



          case DLL_THREAD_DETACH:  



               。。。。。。。  



          case DLL_PROCESS_DETACH:  



               。。。。。。。  



          }  



          return TRUE;  



     }  



     注意:函数名 DllMain 是区分大小写的。许多编程人员有时调用的函数是 DLLMain 。这是一个非 



            常容易犯的错误,因为  DLL              这个词常常使用大写来表示。如果调用的进入点函数不是 



            DllMain ,而是别的函数,代码将能够编译和链接,但是其进入点函数永远不会被调用, 



            DLL 永远不会被初始化。  



     参数 hinstDll 包含了 DLL  的实例句柄。与(w)WinMain 函数的 hinstExe 参数一样,这个 

值用于标识 DLL 的文件映像被映射到进程的地址空间中的虚拟内存地址。通常将这个参数保 



                                                                                               ·253 ·  


…………………………………………………………Page 265……………………………………………………………

Visual C++ 6。0 程序设计从入门到精通  



存在一个全局变量中,这样就可以在调用加载资源函数(如 DialogBox  和 LoadString )时使 

用它。最后一个参数是 fImpLoad,如果 DLL 是隐含加载的,那么该参数将是个非 0 值,如 

果 DLL 是显式加载的,那么它的值是 0 。  

   参 数   fdwReason 用 于 指 明 系 统 为 什 么 调 用 该 函 数 。 该 参 数 可 以 使 用 

DLL_PROCESS_ATTACH  ( 进 程 被 调 用 )、DLL_THREAD_ATTACH  ( 线 程 被 调 用 )、 

DLL_PROCESS_DETACH  (进程被停止)、DLL_THREAD_DETACH  (线程被停止)4 个值的 

其中之一,lpReserved 为保留参数。下面就具体介绍这 4 个值的意义。  



    1.DLL_PROCESS_ATTACH 通知  



    当 DLL 被初次映射到进程的地址空间中时,系统将调用该 DLL 的 DllMain() 函数,给它 

传递参数 fdwReason 的值 DLL_PROCESS_ATTACH 。只有当 DLL 的文件映像初次被映射时, 

才 会 出现 这种 情 况。 如果 线 程在 后来 为 已经 映射 到 进程 的地 址 空间 中的          DLL 调用 

LoadLibrary(Ex) 函 数 , 那 么 操 作 系 统 只 是 递 增 DLL 的 使 用 计 数 , 它 不 会 再 次 用 

DLL_PROCESS_ATTACH 的值来调用 DLL 的 DllMain() 函数。  

    当处理 DLL_PROCESS_ATTACH 时,DLL 应该执行 DLL 中的函数要求的任何与进程相 

关的初始化。例如,DLL 可能包含需要使用它们自己的堆栈(在进程的地址空间中创建 )的 

函数。通过在处理DLL_PROCESS_ATTACH 通知时调用 HeapCreate() 函数,该DLL 的DllMain() 

函数就能够创建这个堆栈。已经创建的堆栈的句柄可以保存在 DLL 函数有权访问的一个全局 

变量中。  

    当 DllMain()函数处理一个 DLL_PROCESS_ATTACH 通知时,DllMain() 的返回值能够指 

明 DLL 的初始化是否已经取得成功。如果对 HeapCreate() 函数的调用取得了成功,DllMain() 

应该返回 TRUE 。如果堆栈不能创建,它应该返回 FALSE 。如果 fdwReason 使用的是其他的 

值,即 DLL_PROCESS_DETACH 、DLL_HREAD_ATTACH 和 DLL_THREAD_DETACH ,那 

么系统将忽略 DllMain()返回的值。  

    当然,系统中的有些线程必须负责执行 DllMain() 函数中的代码。当一个新线程创建时, 

系统将分配进程的地址空间,将 EXE 文件映像和所有需要的 DLL 文件映像映射到进程的地 

址空间中。然后开始创建进程的主线程,并使用该线程调用每个 DLL  的带有 DLL_PROCES  

S_ATTACH 值的 DllMain() 函数。当已经映射的所有 DLL 都对通知信息作出响应后,系统将 

使进程的主线程开始执行可执行模块的 C/C++运行期启动代码,然后执行可执行模块的进入 

点函数(main、wmain 、WinMain 或  wWinMain) 。如果 DLL 的任何一个 DllMain() 函数返回 

FALSE ,则表明初始化没有取得成功,系统便终止整个进程的运行,从它的地址空间中删除 

所有文件映像,给用户显示一个消息框,说明进程无法启动运行。  



   2 .DLL_PROCESS_DETACH 通知  



    当DLL 从进程的地址空间中被卸载时,系统将调用DLL 的DllMain() 函数,给它传递fdwR  

eason 的值 DLL_PROCESS_DETACH 。当 DLL 处理这个值时,它可以执行任何与进程相关的 

清除操作。例如,DLL 可以调用 HeapDestroy() 函数来撤消它在 DLL_PROCESS_DETACH 通 

知期间创建的堆栈。需要注意的是,如果 DllMain() 函数接收到 DLL_PROCESS_DETACH 通 

知时返回 FALSE ,那么 DllMain()就不是用 DLL_PROCESS_DETACH 通知调用的。如果因为 

进程终止运行而使 DLL 被卸载,那么调用 ExitProcess()函数的线程将负责执行 DllMain() 函数 



 ·254 ·  


…………………………………………………………Page 266……………………………………………………………

                                                         第 10 章    动态链接库  



的代码。在正常情况下,这是应用程序的主线程。当进入点函数返回到 C/C++运行期库的启 

动代码时,该启动代码将显
返回目录 上一页 下一页 回到顶部 1 2
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!