DLLMain函數

–轉載自網絡–
DLLMain函數原型:

BOOL WINAPI DllMain(
  HINSTANCE hinstDLL,  // handle to DLL module
  DWORD fdwReason,     // reason for calling function
  LPVOID lpvReserved   // reserved
);

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

簡介:

The DllMain function is an optional method of entry into a dynamic-link library (DLL)。(簡要翻譯:對於動態鏈接庫,DllMain是一個可選的入口函數。)這句話很重要,很多初學者可能都認爲一個動態鏈接庫肯定要有DllMain函數。其實不然,像很多僅僅包含資源信息的DLL是沒有DllMain函數的。

參數說明:

1、HINSTANCE hinstDLL
這個參數是該DLL實例的句柄,也就是此DLL映射到進程地址空間後,在該進程地址空間中的位置。
2、 DWORD fdwReason
此參數標示了調用DllMain函數的原因。有四種值,就是函數中case後的取值。各個取值的含義,稍後論述。
3、 LPVOID lpReserved
保留。

何時調用

系統是在什麼時候調用DllMain函數的呢?靜態鏈接時,或動態鏈接時調用LoadLibrary和FreeLibrary都會調用DllMain函數。DllMain的第二個參數fdwReason指明瞭系統調用Dll的原因,它可能是::
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函數。

爲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幫助文檔查看,搜索時,打鉤“僅搜索標題”會更快定位。
這裏寫圖片描述
按OK後,如果馬上編譯的話會出現如下錯誤:
LIBCMTD.lib(crt0.obj) : error LNK2001: unresolved external symbol _main
Debug/Dll.dll : fatal error LNK1120: 1 unresolved externals
打開VC++菜單Project\Settings\C/C++選項卡,如下圖,在Project Options:末尾的地方添加”/D”(圖中藍色高亮的地方),要注意位置,我試了,要把/D放到/GZ後面也會鏈接錯誤,我也不懂爲什麼,^_^。按OK,再次編譯,成功。大家可以自己測試下到底有沒有更改成功,什麼,如果測試?打出調式信息啊。
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章