一个延迟载入的DLL是隐式链接的,系统一开始不会将该DLL载入,只有当我们的的代码试图去引用DLL包含的一个符号时,系统才会实际载入该DLL。延迟载入DLL在下列情况下非常有用。
- 如果应用程序使用了多个DLL,那么它的初始化可能会比较慢,因为加载程序要将所有必须的DLL映射到进程的地址空间中。
- 如果我们在代码中调用一个新的函数,然后又试图在一个不提供该函数的老版本的操作系统中运行该应用程序,那么加载程序会报告一个错误并且不允许应用程序运行。我们需要一种方法来让应用程序运行,那么就不调用这个不存在的函数。
Windows的延迟载入特性仍然存在一些缺陷:
- 一个导出的字段的DLL是无法延迟载入的;
- Kernel32.dll模块是无法延迟载入的,因为必须载入该模块才能调用LoadLibrary函数和GetProcAddress函数。
- 不应该在DllMain入口点函数中调用一个延迟载入的函数,因为这样可能导致程序崩溃。
为了让延迟载入DLL正常工作,在链接可执行文件的时候,必须修改链接器开关,需要增加两个链接器开关:
/Lib:DelayImp.lib /DelayLoad:MyDll.dll
不可以通过#pragma comment(linker,”“)来设置DelayLoad链接器开关,需要通过在项目属性中设置。
应用程序运行的时候,对延迟载入函数的调用实际上回调用__delayLoadHelper2函数。这个函数会引用那个特殊的延迟载入段,并会先后调用LoadLibrary和GetProcAddress。
为了卸载延迟载入的DLL,必须做两件事,首先必须在构建可执行文件的时候指定一个额外的链接器开关 /Delay:unload,其次,源代码必须调用__FUnloadDelayLoadedDLL2函数。
下面看代码:
//userdefine.h
#ifdef USERDEFINE_EXPORTS
#define USERDEFINE_API extern "C" __declspec(dllexport)
#else
#define USERDEFINE_API extern "C" __declspec(dllimport)
#endif
USERDEFINE_API int fnuserdefine(void);
USERDEFINE_API int g_nResult;
//userdefine.cpp
#define USERDEFINE_API extern "C" __declspec(dllexport)
#include "userdefine.h"
#include <tchar.h>
int g_nResult;
int fnuserdefine(void)
{
int nTmp = 256;
_tprintf(_T("DLL test example\n"));
return nTmp;
}
void IsModuleLoaded(PCTSTR pszModuleName)
{
HMODULE hmod = GetModuleHandle(pszModuleName);
if (hmod == NULL)
{
_tprintf(_T("Dll is not loaded\n"));
}
else
{
_tprintf(_T("Dll is loaded\n"));
}
}
//main.cpp
#include "userdefine.h"
#include <Delayimp.h>
#pragma comment(lib, "delayimp")
#pragma comment(lib,"userdefine.lib")
int _tmain( int argc, TCHAR* argv[] )
{
IsModuleLoaded(_T("userdefine"));
int nRet = fnuserdefine();//调用延迟载入的userdefine.dll
IsModuleLoaded(_T("userdefine"));
_tprintf(_T("nRet=%d\n"), nRet);
__FUnloadDelayLoadedDLL2("userdefine.dll");
IsModuleLoaded(_T("userdefine"));
return 0;
}
main函数开始的时候先利用IsModuleLoaded函数查看userdefine.dll有没有被加载,然后调用动态库函数fnuserdefine,这时候userdefine.dll才被映射到进程的地址空间,利用IsModuleLoaded函数可以看出调用完fnuserdefine函数之后,该动态库已经被映射到该进程的地址空间中,最后卸载userdefine.dll之后,再次调用IsModuleLoaded函数可以发现进程已经卸载userdefine.dll。
最后,贴上程序的运行结果: