按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
。。。。。。。
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++运行期库的启
动代码时,该启动代码将显式调用 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 文件映像必须映射到调用线程的进程
地址空间中。可以用两种方法进行这项操