1.2 显式卸载DLL模块 FreeLibrary
当进程中的线程不再需要 D L L中的引用符号时,可以从进程的地址空间中显式卸载 D L L,方法是调用下面的函数:
BOOL FreeLibrary(HINSTANCE hinstD11);
HINSTANCE:HINSTANCE值,以便标识要卸载的 D L L。该值是较早的时候调用LoadLibrary(Ex)而返回的值。
也可以通过调用下面的函数从进程的地址空间中卸载 DLL:
VOID FreeLibraryAndExitThread( HINSTANCE hinstDll, DWORD dwExitCode );
该函数是在Kernel32.dll中实现的,如下所示:
VOID FreeLibraryAndExitThread(HINSTANCE hinstD11,DWORD dwExitCode){ FreeLibrary(hinstDll); ExitThread(dwExitCode); }
微软创建FreeLibraryAndExitThread原因:
假定你要编写一个 DLL,当它被初次映射到进程的地址空间中时,该 D L L就创建一个线程。当该线程完成它的操作时,它通过调用FreeLibrary函数,从进程的地址空间中卸载该 DLL,并且终止运行,然后立即调用ExitThread。
但是,如果线程分开调用 FreeLibrary和ExitThread,就会出现一个严重的问题。这个问题是调用FreeLibrary会立即从进程的地址空间中卸载 DLL。当调用的FreeLibrary返回时,包含对ExitThread调用的代码就不再可以使用,因此线程将无法执行任何代码。这将导致访问违规,同时整个进程终止运行。
- LoadLibrary和LoadLibraryEx 这两个函数用于对与特定的库相关的进程使用计数进行递增;
- FreeLibrary和FreeLibraryAndExitThread这两个函数则用于对库的每个进程的使用计数进行递减。
第一次加载DLL系统将DLL的文件映像映射到调用进程的地址空间中,并将 D L L的使用计数设置为1。
第二次同一进程的线程加载同一DLL,则不会再次映射DLL到进程的地址空间,系统只是将与该进程的D L L相关的使用计数递增1。
GetModuleFileName
调用GetModuleFileName函数线程就能够确定D L L是否已经被映射到进程的地址空间中。
只有当尚未MyLib.dll被映射到进程的地址空间,才LoadLibrary
HINSTANCE hinstDll = GetModu1eHand1e("MyLib"); // DLL extension assumed if (hinstDll == NULL) { hinstDll = LoadLibrary("MyLib");ll DLL extension assumed}
如果只有 DLL的HINSTANCE值,那么可以调用 GetModuleFileName函数,确定 DLL(或. e x e)的全路径名:
DWORD GetModuleFi1eName( HINSTANCE hinstModule, PTSTR pszPathName, DWORD cchPath );
hinstModule:是D L L(或. e x e)的HINSTANCE 。
pszPathName:是该函数将文件映像的全路径名放入的缓存的地址。
cchPath:用于设定缓存的大小(以字符为计量单位)。
1.3 显式链接到一个输出符号
一旦D L L模块被显式加载,线程就必须获取它要引用的符号的地址,方法是调用下面的函数:
FARPROC GetProcAddress( HINSTANCE hinstD11,PCSTR pszSymbo1Name );
hinstDll:调用LoadLibrary (Ex)或GetModuleFi1eName函数而返回的,它用于设定包含符号的D L L的句柄。
pszSymbolName:
- 以 0结尾的字符串
FARPROC pfn = GetProcAddress(hinstD11,"SomeFuncInDll");
- 用于指明你想要其地址的符号的序号
FARPROC pfn = GetProcAddress(hinstDll,MAKEINTRESOURCE(2));
调用GetProcAddress的第一种方法比第二种方法要慢。
但非常不建议使用序号,因为当传递的 序号尚未分配给任何导出的函数,会返回非NULL值,误以为你已经拥有一个有效的地址。
二、DLL的进入点函数 DllMain
如果你创建一个只包含资源的DLL,就不必实现该函数。
如果确实需要在 D L L中接受通知信息,可以实现类似下面的进入点函数:
B00L WINAPI DllMain(HINSTANCE hinstDll,DWORD fdwReason,PVOID fImpLoad){ switch (fdwReason){ case DLL_PROCESS_ATTACH: //The DLL is being mapped into the process's address space. break; case DLL_THREAD_ATTACH: //A thread is being created. break; case DLL_THREAD_DETACH: //A thread is exiting cleanly. break; case DLL_PROCESS_DETACH: //The DlL is being unmapped from the process's address space. break; } return(TRUE);//Used only for DLL_PROCESS_ATTACH }
hinstDll:包含了D L L的实例句柄,这个值用于标识D L L的文件映像被映射到进程的地址空间中的虚拟内存地址。
fImpLoad:如果D L L是隐含加载的,那么该参数将是个非 0值,如果D L L是显式加载的,那么它的值是0。
fdwReason:用于指明系统为什么调用该函数。该参数可以使用 4个值中的一个。DLL_PROCESS_ATTACH、DLL_THREAD_DETACH、DLL_THREAD_DETACH、DLL_PROCESS_DETACH。
注意:
必须记住,DLL使用DllMain函数来对它们进行初始化。
当你的DllMain函数执行时,同一个地址空间中的其他DLL可能尚未执行它们的DllMain函数。
这意味着它们尚未初始化,因此你应该避免调用从其他DLL中输入的函数。此外,你应该避免从DllMain内部调用LoadLibrary(Ex)和FreeLibrary函数,因为这些函数会形式一个依赖性循环。
Platform SDK文档说,你的D l l M a i n函数只应该进行一些简单的初始化,比如设置本地存储器(第 2 1章介绍),创建内核对象和打开文件等。你还必须避免调用 User、Shell、ODBC、COM、RPC和套接字函数(即调用这些函数的函数),因为它们的DLL也许尚未初始化 ,或者这些函数可能在内部调用LoadLibrary(Ex)函数,这同样会形成一个依赖性循环。
2.1 DLL_PROCESS_ATTACH通知
- 当DLL被初次映射到进程的地址空间中时,系统将调用该 DLL的DllM a i n函数,给它传递参数fdwReason的值DLL_PROCESS_ATTACH。
- 当处理DLL_PROCESS_ATTACH时,D L L应该执行D L L中的函数要求的任何与进程相关的初始化。
- 当处理DLL_PROCESS_ATTACH时,如果DLL的任何一个DllMain函数返回FALSE,指明初始化没有取得成功,系统便终止整个进程的运行。LoadLibrary就会返回NULL。
2.2 DLL_PROCESS_DETACH通知
- D L L从进程的地址空间中被卸载时,系统将调用 D L L的DllMain函数,给它传递fdwReason的值DLL_PROCESS_DETACH。当D L L处理这个值时,它应该执行任何与进程相关的清除操作。
注意:
如果因为系统中的某个线程调用了TerminateProcess而使进程终止运行,那么系统将不调用带有DLL_PROCESS_DETACH值的D L L的D l l M a i n函数。这意味着映射到进程的地址空间中的任何D LL都没有机会在进程终止运行之前执行任何清除操作。这可能导致数据的丢失。只有在迫不得已的情况下,才能使用 TerminateProcess函数。
2.3 DLL_THREAD_ATTACH通知
当在一个进程中创建线程时,系统要查看当前映射到该进程的地址空间中的所有 DLL文件映像,并调用每个文件映像的带有DLL_THREAD_ATTACH值的DllMain函数。
2.4 DLL_THREAD_DETACH通知
2.5 顺序调用DllMain
2.6 DllMain与C/C++运行期库
三、延迟加载DLL
介绍
VC++6.0的特性——延迟加载DLL。
它能够使 D L L的操作变得更加容易。这个特性称为延迟加载 D L L。延迟加载的 D L L是个隐含链接的 D L L,它实际上要等到你的代码试图引用 D L L中包含的一个符号时才进行加载。
应用场景
以下情况延迟加载是有用的:
- 应用初始化加载的DLL过多,导致程序初始化时间过长。使用延迟加载,在程序不同阶段分时加载DLL可以解决这个问题。
- 新版本系统的函数,在老版本系统中不存在时,使用延迟加载,在加载dll之前判断系统版本,不存在则不运行新函数。
特性
四、函数转发器
函数转发器是D L L的输出节(导出段)中的一个项目,用于将对一个函数的调用转至另一个 DLL中的另一个函数。
五、已知的DLL
操作系统提供的某些D L L得到了特殊的处理。这些D L L称为已知的D L L。它们与其他D L L基本相同,但是操作系统总是在同一个目录中查找它们,以便对它们进行加载操作。
六、DLL转移
D L L转移特性能够强制操作系统的加载程序首先从你的应用程序目录中加载文件模块。只有当加载程序无法在应用程序目录中找到该文件时,它才搜索其他目录。
为了强制加载程序总是首先查找应用程序的目录,要做的工作就是在应用程序的目录中放入一个文件。该文件的内容可以忽略,但是该文件必须称为 AppName.local。例如,如果有一个可执行文件的名字是 SuperApp.exe,那么转移文件必须称为SuperApp.exe.local。
七、改变模块的位置
每个可执行模块和D L L模块都有一个首选的基地址,用于标识模块应该映射到的进程地址空间中的理想内存地址。当创建一个可执行模块时,链接程序将该模块的首选基地址设置为0x0040 0000。如果是DLL模块,链接程序设置的首选基地址是 0x1000 0000。
八、绑定模块
总结
1.LoadLibrary和LoadLibraryEx区别?