20.1 DLL模块的显式加载和符号链接--《Windows核心编程》
一、显式加载DLL模块使用函数 LoadLibrary/LoadLibraryEx
HINSTANCE LoadLibrary(PCTSTR pSzDLLPathName);
HINSTANCE LoadLibraryEx(
PCTSTR pSzDLLPathName,
HANDLE hF1le,
DWORD dwF1ags
);
pSzDLLPathName:加载的dll文件的路径。
hFile:保留供将来使用,现在是 NULL。
dwFlags:必须将它设置为 0 。或者设置 DONT_RESOLVE_DLL_REFERENCES、 LOAD_LIBRARY_AS_DATAFILE 和 LOAD_WITH_ALTERED_SEARCH_PATH 等标志的一个组合。
(1)DONT_RESOLVE_DLL_REFERENCES:标志使系统只需将 DLL 映射到调用进程的地址空间,而不用调用 DllMain(通常会调用)。
(2)LOAD_LIBRARY_AS_DATAFILE:系统只是将 DLL 映射到进程的地址空间中,就像它是数据文件一样。系统并不花费额外的时间来准备执行文件中的任何代码。如果不指定 LOAD_LIBRARY_AS_DATAFILE 标志,系统会认为需要执行文件中的代码,并用相应的方式来设置页面保护属性。例如使用此标志载入DLL,当对这个 DLL 调用 GetProcAddress 的时候,返回值会是 NULL,而 GetLastError 将会返回 ERROR_MOD_NOT_FOUND。
由于下面几个原因,该标志是非常有用的:
- 加载只有资源没有函数的DLL时: 首先,如果有一个DLL(它只包含资源,但不包含函数),那么可以设定这个标志,使 DLL的文件映像能够映射到进程的地址空间中。
- 调用加载资源的函数: 然后可以在调用加载资源的函数时,使用LoadLibraryEx函数返回的HINSTANCE值。
- 加载EXE访问其中资源: 通常情况下,加载一个.exe文件,就能够启动一个新进程,但是也可以使用 LoadLibraryEx函数将.exe文件的映像映射到进程的地址空间中。借助映射的 .exe文件的HINSTANCE值,就能够访问文件中的资源。由于.exe文件没有DllMain函数,因此,当调用LoadLibraryEx来加载一个.exe文件时,必须设定 LOAD_LIBRARY_AS_DATAFILE 标志。
(3)LOAD_WITH_ALTERED_SEARCH_PATH:用于改变 LoadLibraryEx用来查找特定的DLL文件时使用的搜索算法。如果设定了该标志,那么LoadLibraryEx 函数就按照下面的顺序来搜索文件:
- pszDLLPathName参数中设定的目录。
- 进程的当前目录。
- Windows的系统目录。
- Windows目录。
- PATH环境变量中列出的目录。
(4)LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE:与 LOAD_LIBRARY_AS_DATAFILE 相似,但是是以独占方式打开 DLL 文件,禁止任何其他程序在当前程序使用该 DLL 文件的时候对其修改。
(5)LOAD_LIBRARY_AS_IMAGE_RESOURCE:与 LOAD_LIBRARY_AS_DATAFILE 相似,但是载入 DLL 的时候,会对 RVA 进行修复(变成 RAW)。这样 RVA 可以直接使用,不必再根据 DLL 载入到的内存地址来对它们进行转换了。当需要对 DLL 进行解析来遍历 PE 段时,这个标志特别有用。
二、显式卸载DLL模块 FreeLibrary
BOOL FreeLibrary(HINSTANCE hinstD11);
也可以通过调用下面的函数从进程的地址空间中卸载 DLL,该函数是在Kernel32.dll中实现的:
VOID FreeLibraryAndExitThread(HINSTANCE hinstDll, DWORD dwExitCode);
微软创建 FreeLibraryAndExitThread 原因:
假定你要编写一个 DLL,当它被初次映射到进程的地址空间中时,该 D L L就创建一个线程。当该线程完成它的操作时,它通过调用FreeLibrary函数,从进程的地址空间中卸载该 DLL,并且终止运行,然后立即调用ExitThread。但是,如果线程分开调用 FreeLibrary和ExitThread,就会出现一个严重的问题。这个问题是调用FreeLibrary会立即从进程的地址空间中卸载 DLL。当调用的FreeLibrary返回时,包含对ExitThread调用的代码就不再可以使用(指DLL中函数的代码),因此线程将无法执行任何代码。这将导致访问违规,同时整个进程终止运行。
而使用此函数,FreeLibrary 返回时,下一条要执行的指令在 Kernel32.dll 中,可以继续调用 ExitThread。
关于 DLL 使用计数:只有使用计数递减至0,DLL才会从进程空间中撤销映像。
LoadLibrary和LoadLibraryEx 这两个函数用于对与特定的库相关的进程使用计数进行递增;
FreeLibrary和FreeLibraryAndExitThread这两个函数则用于对库的每个进程的使用计数进行递减。
其他函数
GetModuleHandle 函数可以根据名称返回加载的DLL的句柄(地址),当然也可以检查一个DLL是否被映射到进程的地址空间中。
GetModuleFileName 函数可以根据DLL句柄返回DLL完整路径。