2012年7月9日 星期一

DLL入口函數DllMain


  1. DllMain簡介
        跟exe有個main或者WinMain入口函數一樣,DLL也有一個入口函數,就是DllMain。以「DllMain」為關鍵字,來看看MSDN幫助文檔怎麼介紹這個函數的:The DllMain function is an optional method of entry into a dynamic-link library (DLL)。(翻譯:DllMain函數是DLL文件的入口函數,它是可選的。)這句話很重要,很多初學者可能都認為一個動態鏈接庫肯定要有DllMain函數。其實不然,像很多僅僅包含資源信息的DLL是沒有DllMain函數的。
  2. 何時調用DllMain
        系統是在什麼時候調用DllMain函數的呢?靜態鏈接或動態鏈接時調用LoadLibrary和FreeLibrary都會調用DllMain函數。 DllMain的第二個參數fdwReason指明了系統調用Dll的原因,它可能是DLL_PROCESS_ATTACH、 DLL_PROCESS_DETACH、DLL_THREAD_ATTACH和DLL_THREAD_DETACH。
  3. 以下從這四種情況來分析系統何時調用了DllMain。
    (1) 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函數。
    (2) DLL_PROCESS_DETACH
        當DLL被從進程的地址空間解除映射時,系統調用了它的DllMain,傳遞的fdwReason
    值是DLL_PROCESS_DETACH。當DLL處理該值時,它應該執行進程相關的清理工作。
        那麼什麼時候DLL被從進程的地址空間解除映射呢?兩種情況:
           ◆FreeLibrary解除DLL映射(有幾個LoadLibrary,就要有幾個FreeLibrary)
           ◆進程結束而解除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函數。
    (3) 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函數,哪怕是線程中建立線程也一樣。
    (4) DLL_THREAD_DETACH
        如果線程調用了ExitThread來結束線程(線程函數返回時,系統也會自動調用ExitThread),系統查看當前映射到進程空間中的所有DLL文件映像,並用DLL_THREAD_DETACH來調用DllMain函數,通知所有的DLL去執行線程級的清理工作。
        注意:如果線程的結束是因為系統中的一個線程調用了TerminateThread,系統就不會用值DLL_THREAD_DETACH來調用所有DLL的DllMain函數。

沒有留言:

張貼留言