簡介
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
何時調用
DLL_PROCESS_ATTACH、
DLL_PROCESS_DETACH、
DLL_THREAD_ATTACH、
DLL_THREAD_DETACH。
以下從這四種情況來分析系統何時調用了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函式。
進程卸載
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函式。
執行緒映射
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函式,哪怕是執行緒中建立執行緒也一樣。
執行緒卸載
DLL_THREAD_DETACH
如果
執行緒調用了
ExitThread來結束執行緒(執行緒函式返回時,系統也會自動調用ExitThread),系統查看當前映射到進程空間中的所有DLL檔案映像,並用DLL_THREAD_DETACH來調用DllMain函式,通知所有的DLL去執行執行緒級的清理工作。
注意:如果執行緒的結束是因為系統中的一個執行緒調用了
TerminateThread,系統就不會用值DLL_THREAD_DETACH來調用所有DLL的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函式是DLL模組的默認
入口點。當Windows載入DLL模組時調用這一函式。系統首先調用全局對象的
構造函式,然後調用
全局函式DLLMain。DLLMain函式不僅在將DLL連結載入到進程時被調用,在DLL模組與進程分離時(以及其它時候)也被調用。下面是一個框架DLLMain函式的例子。
如果我們在DllMain中寫入下面的代碼(在原來的gan
dll.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);
}
#include<stdio.h>
#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:\gan
dll\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)的喔,是應用程式一上來就調用的,不是用到該函式時才調用的!