DLL延遲加載技術

一個延遲載入的DLL是隱式鏈接的,系統一開始不會將該DLL載入,只有當我們的的代碼試圖去引用DLL包含的一個符號時,系統纔會實際載入該DLL。延遲載入DLL在下列情況下非常有用。

  1. 如果應用程序使用了多個DLL,那麼它的初始化可能會比較慢,因爲加載程序要將所有必須的DLL映射到進程的地址空間中。
  2. 如果我們在代碼中調用一個新的函數,然後又試圖在一個不提供該函數的老版本的操作系統中運行該應用程序,那麼加載程序會報告一個錯誤並且不允許應用程序運行。我們需要一種方法來讓應用程序運行,那麼就不調用這個不存在的函數。

Windows的延遲載入特性仍然存在一些缺陷:

  1. 一個導出的字段的DLL是無法延遲載入的;
  2. Kernel32.dll模塊是無法延遲載入的,因爲必須載入該模塊才能調用LoadLibrary函數和GetProcAddress函數。
  3. 不應該在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。
最後,貼上程序的運行結果:
這裏寫圖片描述

發佈了65 篇原創文章 · 獲贊 9 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章