複製鏈接
請複製以下鏈接發送給好友

DllMain

鎖定
exe有個main或者WinMain入口函數一樣,DLL也有一個入口函數,就是DllMain。以“DllMain”為關鍵字,來看看MSDN幫助文檔怎麼介紹這個函數的。
外文名
DllMain
含    義
就是DllMain

DllMain簡介

The DllMain function is an optional method of entry into a dynamic-link library (DLL)。(簡要翻譯:對於動態鏈接庫,DllMain是一個可選的入口函數。)這句話很重要,很多初學者可能都認為一個動態鏈接庫肯定要有DllMain函數。其實不然,像很多僅僅包含資源信息的DLL是沒有DllMain函數的。
函數定義
BOOL WINAPI DllMain(
  _In_ HINSTANCE hinstDLL, // 指向自身的句柄
  _In_ DWORD fdwReason, // 調用原因
  _In_ LPVOID lpvReserved // 隱式加載和顯式加載
);
// 以上內容來自MSDN

DllMain何時調用

系統是在什麼時候調用DllMain函數的呢?靜態鏈接時,或動態鏈接時調用LoadLibraryFreeLibrary都會調用DllMain函數。DllMain的第二個參數fdwReason指明瞭系統調用Dll的原因,它可能是::
DLL_PROCESS_ATTACH、
DLL_PROCESS_DETACH、
DLL_THREAD_ATTACH、
DLL_THREAD_DETACH。
以下從這四種情況來分析系統何時調用了DllMain。

DllMain進程映射

DLL_PROCESS_ATTACH
大家都知道,一個程序要調用Dll裏的函數,首先要先把DLL文件映射到進程的地址空間。要把一個DLL文件映射到進程的地址空間,有兩種方法:靜態鏈接動態鏈接的LoadLibrary或者LoadLibraryEx。
當一個DLL文件被映射到進程的地址空間時,系統調用該DLL的DllMain函數,傳遞的fdwReason參數為DLL_PROCESS_ATTACH,這種調用只會發生在第一次映射時。如果同一個進程後來為已經映射進來的DLL再次調用LoadLibrary或者LoadLibraryEx,操作系統只會增加DLL的使用次數,它不會再用DLL_PROCESS_ATTACH調用DLL的DllMain函數。不同進程用LoadLibrary同一個DLL時,每個進程的第一次映射都會用DLL_PROCESS_ATTACH調用DLL的DllMain函數。
可參考DllMainTest的DLL_PROCESS_ATTACH_Test函數。

DllMain進程卸載

DLL_PROCESS_DETACH
當DLL被從進程的地址空間解除映射時,系統調用了它的DllMain,傳遞的fdwReason值是DLL_PROCESS_DETACH。當DLL處理該值時,它應該執行進程相關的清理工作。
那麼什麼時候DLL被從進程的地址空間解除映射呢?兩種情況:
◆FreeLibrary解除DLL映射(有幾個LoadLibrary,就要有幾個FreeLibrary)
◆進程結束而解除DLL映射,在進程結束前還沒有解除DLL的映射,進程結束後會解除DLL映射。(如果進程的終結是因為調用了TerminateProcess,系統就不會用DLL_PROCESS_DETACH來調用DLL的DllMain函數。這就意味着DLL在進程結束前沒有機會執行任何清理工作。)
注意:當用DLL_PROCESS_ATTACH調用DLL的DllMain函數時,如果返回FALSE,説明沒有初始化成功,系統仍會用DLL_PROCESS_DETACH調用DLL的DllMain函數。因此,必須確保清理那些沒有成功初始化的東西。
可參考DllMainTest的DLL_PROCESS_DETACH_Test函數。

DllMain線程映射

DLL_THREAD_ATTACH
當進程創建一線程時,系統查看當前映射到進程地址空間中的所有DLL文件映像,並用值DLL_THREAD_ATTACH調用DLL的DllMain函數。
新創建的線程負責執行這次的DLL的DllMain函數,只有當所有的DLL都處理完這一通知後,系統才允許進程開始執行它的線程函數。
注意跟DLL_PROCESS_ATTACH的區別,我們在前面説過,第n(n>=2)次以後地把DLL映像文件映射到進程的地址空間時,是不再用DLL_PROCESS_ATTACH調用DllMain的。而DLL_THREAD_ATTACH不同,進程中的每次建立線程,都會用值DLL_THREAD_ATTACH調用DllMain函數,哪怕是線程中建立線程也一樣。

DllMain線程卸載

DLL_THREAD_DETACH
如果線程調用了ExitThread來結束線程(線程函數返回時,系統也會自動調用ExitThread),系統查看當前映射到進程空間中的所有DLL文件映像,並用DLL_THREAD_DETACH來調用DllMain函數,通知所有的DLL去執行線程級的清理工作。
注意:如果線程的結束是因為系統中的一個線程調用了TerminateThread,系統就不會用值DLL_THREAD_DETACH來調用所有DLL的DllMain函數。

DllMain換名

在早期的SDK版本中,DllMain是叫做DllEntryPoint。其實有一件鮮為人知的事:一個Dll的入口函數名是可以自己定義的。下面我將以VC++6.0為例來演示如何更改。首先要説明一點,雖然DllMain可以換成其他函數名,但函數的參數和返回值必須和DllMain一樣。而且這個函數要為__stdcall類型(DllMain本身也是__stdcall類型)。
打開VC++菜單Project\Settings\Link tab\ Output in the Category box,在Entry-point symbol中輸入要替換DllMain的函數名(當然這個函數名是你程序中已經實現的函數)。Entry-point symbol是幹麼的呢?可以以關鍵字“Entry-point symbol”搜索MSDN幫助文檔查看,搜索時,打鈎“僅搜索標題”會更快定位。

DllMain使用

DllMain函數是DLL模塊的默認入口點。當Windows加載DLL模塊時調用這一函數。系統首先調用全局對象的構造函數,然後調用全局函數DLLMain。DLLMain函數不僅在將DLL鏈接加載到進程時被調用,在DLL模塊與進程分離時(以及其它時候)也被調用。下面是一個框架DLLMain函數的例子。
如果我們在DllMain中寫入下面的代碼(在原來的gandll.c中添加下面的代碼):
BOOL APIENTRY DllMain(HANDLEhModule, DWORD ul_reason_for_call, LPVOIDlpReserved)
{
  printf("hModule.%p lpReserved.%p \n", hModule,lpReserved);
  switch (ul_reason_for_call)
  {
    case DLL_PROCESS_ATTACH:
      printf("Process attach. \n");
      break;
    case DLL_PROCESS_DETACH:
      printf("Process detach. \n");
      break;
    case DLL_THREAD_ATTACH:
      printf("Thread attach. \n");
      break;
    case DLL_THREAD_DETACH:
      printf("Thread detach. \n");
      break;
  }
  return (TRUE);
}
同時將dlltest\dlltest.c修改為:
#include "dlltest.h"
int main(int argc, char**argv){
  printf("Simple DLL test start. \n");
  printf("Call DLL function: \n");
  printf("Test DLL values: %d \n", add2(1, 2));
  printf("Call DLL function end. \n");
  printf("Simple DLL test end. \n");
  return (0);
}
我簡單的測試一下輸出結果為:
C:\gandll\dlltest>dlltest
hModule.10000000lpReserved.0012FD30
Process attach.
Simple DLL teststart.
Call DLLfunction:
Test DLL values:3
Call DLL functionend.
Simple DLL testend.
hModule.10000000lpReserved.00000001
Process detach.
也就是説DLL加載和應用程序退出的使用都會調用該函數(DllMain)的哦,是應用程序一上來就調用的,不是用到該函數時才調用的!