八宝书库 > 文学其他电子书 > VC语言6.0程序设计从入门到精通 >

第58部分

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

小说: VC语言6.0程序设计从入门到精通 字数: 每页4000字

按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!




关的初始化。例如,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++运行期库的启 

动代码时,该启动代码将显式调用 ExitProcess()函数,终止进程的运行。  

    如果因为进程中的线程调用 FreeLibrary()或 FreeLibraryAndExitThread() 函数而将 DLL 卸 

载,那么调用函数的线程将负责执行 DllMain()函数的代码。如果使用 FreeLibrary ,那么要等 

到  DllMain() 函数完成对   DLL_PROCESS_DETACH   通知的执行后,该线程才可以从对 

FreeLibrary 函数的调用中返回。  



    3 .DLL_THREAD_ATTACH 通知  



    当在一个进程中创建线程时,系统查看当前映射到该进程的地址空间中的所有 DLL 文件 

映像,并调用每个带有 DLL_THREAD_ATTACH  值的 DllMain() 函数文件映像。这样,DLL 

就可以执行每个线程的初始化操作。新创建的线程负责执行 DLL  的所有 DllMain()函数中的 

代码。只有当所有的 DLL 都有机会处理该通知时,系统才允许新线程开始执行它的线程函数。  

    当一个新 DLL 被映射到进程的地址空间时,如果该进程内已经有若干个线程正在运行, 

那么系统将不为现有的线程调用带有 DLL_THREAD_ATTACH 值的 DDL 的 DllMain()函数。 

只 有 当 新 线 程 创 建 时 , DLL   被 映 射 到 进 程 的 地 址 空 间 中 , 它 才 可 以 调 用 带 有 

DLL_THREAD_ATTACH 值的 DLL 的 DllMain() 函数。  

    另外要注意,系统并不为进程的主线程调用带有 DLL_THREAD_ATTACH 值的任何 DllMain() 

函数。进程初次启动时映射到进程的地址空间中的任何 DLL 均接收 DLL_PROCESS_ATTACH 通 

知,而不是 DLL_THREAD_ATTACH 通知。  



    4 .DLL_THREAD_DETACH 通知  



    让线程终止运行的首选方法是使它的线程函数返回。这使得系统可以调用  ExitThread() 

函数来撤消该线程。如果 ExitThread() 函数要终止运行该线程,系统不会立即将它撤消,而是 

取 出 这 个 即 将 被 撤 消 的 线 程 , 并 让 它 调 用 已 经 映 射 的          DLL  中 所 有 带 有 

DLL_THREAD_DETACH 值的 DllMain() 函数。这个通知告诉所有的 DLL 执行每个线程的清 

除操作。例如,DLL 版本的 C/C++运行期库能够释放它用于管理多线程应用程序的数据块。  



    注意:DLL 能够防止线程终止运行。例如,当 DllMain() 函数接收到 DLL_THREAD_DETACH 通 



        知时,它就能够进入一个无限循环。只有当每个 DLL  已经完成对 DLL_THREAD_DETACH 



        通知的处理时,操作系统才会终止线程的运行。  



    如 果 当  DLL 被 撤 消 时 仍 然 有 线 程 在 运 行 , 那 么 就 不 会 有 任 何 线 程 调 用 带 有 

DLL_THREAD_DETACH 值的 DllMain() 函数。可以在进行 DLL_THREAD_DETACH 的处理 

时查看这个情况,这样就能够执行必要的清除操作。  



10。2。2    DLL 的导出函数  



    当 Microsoft 的 C/C++编译器看到变量、函数原型或 C++类之前的这个修改符的时候,它 

就将某些附加信息嵌入产生的。obj 文件中。当链接 DLL 的所有。obj 文件时,链接程序将对这 

些信息进行分析。  

      



                                                                  ·255 ·  


…………………………………………………………Page 267……………………………………………………………

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



     当 DLL  被链接时,链接程序要查找关于输出变量、函数或  C++类的信息,并自动生成 

一个。lib 文件。该。lib 文件包含一个 DLL 输出的符号列表。当然,如果要链接引用该 DLL 的 

输出符号的任何可执行模块,该。lib 文件是必不可少的 。除了创建。lib 文件外,链接程序还要 

将一个输出符号表嵌入产生的 DLL 文件。这个输出节包含输出变量、函数和类符号的列表(按 

字母顺序排列)。该链接程序还将能够找到每个符号的相对虚拟地址(RVA ),并在该地址中 

放入 DLL 模块。  

    使用 Microsoft 的 Visual Studio 的 DumpBin。exe 实用程序(带有…exports 开关),能够看到 

DLL 的输出节是个什么样子。Kernel32。dll 的输出结果如图 10…1 所示。  



                                                                       



                         图 10…1    dumpbin 输出动态链接库的导出函数  



    通过 Visual  Studio 所提供的 Dependency  Walker 的可视化工具也可以查看动态链接库的 

导出函数信息,如图 10…2 所示。  



                                                                         



                     图 10…2    利用 Dependency Walker 工具查看导出函数信息  



 ·256 ·  


…………………………………………………………Page 268……………………………………………………………

                                                                  第 10 章    动态链接库  



10。3    两种链接 DLL 的方式  



    如果线程需要调用 DLL 模块中的函数,那么 DLL 文件映像必须映射到调用线程的进程 

地址空间中。可以用两种方法进行这项操作 。第一种方法是让应用程序的源代码只引用 DLL 

中包含的符号。这样,当应用程序启动运行时,加载程序就能够隐含加载(或链接)需要的 

DLL 。第二种方法是在运行时让应用程序显式加载需要的 DLL 并且显式链接到需要的输出符 

号。换句话说,当应用程序运行时,其中的线程决定它是否要调用 DLL 中的函数。该线程可 

以将 DLL 显式加载到进程的地址空间,获得 DLL  中包含的函数的虚拟内存地址,然后使用 

该内存地址调用该函数。该方法的一切操作都是在应用程序运行时进行的。  

    当线程加载动态链接库的时候,是按照下面的搜索顺序查找并加载动态链接库文件的。  

    o  当前目录下(首先将动态链接库拷贝至 DEBUG  目录下,因为可执行文件在该目录下)。  

    o  Windows  目录。  

    o  Windows 系统目录。  

    o  PATH 环境变量中设置的目录。  

    o  列入映射网络的目录表中的目录。  

    下面将介绍隐式链接和显式链接这两种调用 DLL 的方式。  



10。3。1    隐式链接  



    如果程序员采用隐式链接方式建立一个 DLL 文件,链接程序会自动生成一个与之对应的 

LIB 导入文件。该文件包含了每一个 DLL 导出函数的符号名和可选的标识号,但是并不含有 

            LIB 文件作为 DLL 的替代文件被编译到应用程序项目中。当程序员通过静态链 

实际的代码。 

接方式编译生成应用程序时,应用程序中的调用函数与 LIB 文件中导出符号相匹配,这些符 

号或标识号进入生成的 EXE 文件中。LIB 文件中也包含了对应的 DLL 文件名(但不是完全 

的路径名),链接程序将其存储在 EXE 文件内部。当应用程序运行过程中需要加载 DLL 文件 

时,Windows 根据这些信息发现并加载 DLL ,然后通过符号名或标识号实现对 DLL  函数的 

动态链接。  

    下面的例子通过隐式链接调用 MyDll。dll 库中的 Min 函数。首先生成一个 TestDll 项目, 

在 DllTest。h 、DllTest。cpp 文件中分别输入如下代码:  



    //Dlltest。h  



    #pragma ment(lib ,〃MyDll。lib〃)  



    extern 〃C〃_declspec(dllimport) int Max(int a;int b);  



    extern 〃C〃_declspec(dllimport) int Min(int a;int b);  



    //TestDll。cpp  



    #include〃Dlltest。h〃  



    void main()  



    {  



        int a;  



        a=min(8;10);  



                                                                             ·257 ·  


…………………………………………………………Page 269……………………………………………………………

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



         printf(〃 比较的结果为%dn〃,a);  



     }  



       在创建 DllTest。exe 文件之前,要先将 MyDll。dll 和 MyDll。lib 拷贝到当前工程所在的目 

录下,也可以拷贝到 windows 的 System 目录下。如果DLL 使用的是 DEF 文件,要删除 TestDll。h 

文件中关键字 extern 〃C〃 。TestDll。h 文件中的关键字 Progam mit 是要 Visual C++ 的编译器 

在  link 时,链接到  MyDll。lib       文件。当然,开发人员也可以不使用#pragma  ment(lib , 

〃MyDll。lib〃)语句,而直接在工程的 Setting→Link 页的 Object/Moduls 栏填入 MyDll。lib 即可。  



10。3。2    显式链接  



     显式链接方式对于集成化的开发语言(例如 Visual Basic )比较适合。有了显式链接,程 

序员就不必再使用导入文件,而是直接调用 Win32  的 LoadLibary 函数,并指定 DLL  的路径 

作为参数。LoadLibary 返回 HINSTANCE 参数,应用程序在调用 GetProcAddress 函数时使用 

这一参数。GetProcAddress 函数将符号名或标识号转换为 DLL  内部的地址。假设有一个导出 

如下函数的 DLL 文件:  



     extern 〃C〃 __declspec(dllexport) double SquareRoot(double d);  



     在隐式链接方式中,所有被应用程序调用的 DLL 文件都会在应用程序 EXE 文件加载时 

被加载在到内存中 。但如果采用显式链接方式,程序员可以决定 DLL 文件何时加载或不加载。 

显式链接在运行时决定加载哪个 DLL 文件。例如,可以将一个带有字符串资源的 DLL 模块 

以英语加载,而另一个以西班牙语加载。应用程序在用户选择了合适的语种后再加载与之对 

应的 DLL 文件。  

     在显式链接方式中,应用程序在执行过程中随时可以加载  DLL                                 文件,也可以随时卸载 

DLL 文件,这是隐式链接所无法作到的,所以显式链接具有更好的灵活性,更适合解释性语 

言。不过实现显式链接比较复杂,除了要调用特定的Win32 的LoadLibrary 函数动态链接DLL , 

在应用程序退出之前,还应该用 FreeLibrary 或 MFC 提供的 AfxFreeLibrary 释放动态链接库。 

下面是通过显式链接调用 DLL 中的 Max 函数的例子,代码如下:  



     #include …  



    

返回目录 上一页 下一页 回到顶部 0 0

你可能喜欢的