DLL的高级操作技术——Windows核心编程学习手札之二十
DLL的高級操作技術
——Windows核心編程學習手札之二十
顯示加載DLL模塊:
?????? HINSTANCE LoadLibrary(PCTSTR pszDLLPathName);
?????? HINSTANCE LoadLibraryEx(
??????????????????????????? PCTSTR pszDLLPathName,
??????????????????????????? HANDLE hFile,
??????????????????????????? DWORD dwFlags);
上面兩個函數找出用戶系統上的文件映像,并將DLL的文件映像映射到調用進行的地址空間中,函數返回的HINSTANCE值用于標識文件映像映射到的虛擬內存地址,如果DLL不能被映射到進程的地址空間,則返回NULL,調用GetLastError可了解詳細錯誤信息。LoadLibraryEx函數中的參數hFile保留使用,現在傳遞NULL;參數dwFlags有如下設置意義:
1)DON’T_RESOLVE_DLL_REFERENCES
傳遞該參數告訴系統將DLL映射到調用進程的地址空間中,有兩點作用:一是DLL被映射到進程的地址空間中,通常要調用DLL的入口函數DllMain,該標志則告訴系統不需要調用DllMain;二是當DLL內部還需要調用另一個DLL,系統一般會自動加載這些被該DLL所調用的DLL,該標志告訴系統不自動將其他DLL加載到進程的地址空間中。
2)LOAD_LIBIRARY_AS_DATAFILE
該標志在下面情況是有用的:只包含資源不包含函數的DLL,傳遞該標志下使DLL的文件映像能夠映射到進程的地址空間中。
3)LOAD_WITH_ALTERED_SEARCH_PATH
設置該標志按照下面順序搜索文件:pszDLLPathName參數中設定的目錄,進程的當前目錄,Windows的系統目錄,Windows目錄,Path環境變量中列出的目錄;
顯示卸載DLL模塊:
當進程中的線程不再需要DLL中的引用符號時,可以從進程的地址空間中顯示卸載DLL:
?????? BOOL FreeLibrary(HINSTANCE hinstDll);
傳遞HINSTANCE值,以便標識要卸載的DLL,也通過調用下面的函數從進程的地址空間中卸載DLL:
?????? VOID FreeLibraryAndExitThread(
??????????????????????????????????????????????????????? HINSTANCE hinstDll,
??????????????????????????????? DWORD dwExitCode);
該函數是在Kernel32.dll中實現的,如下:
?????? VOID FreeLibraryAndExitThread(HINSTANCE hinstDll,
??????????????????????????????? DWORD dwExitCode){
??????????????????????????????????????????????????????? FreeLibrary(hinstDll);
???????????????????????????? ???ExitThread(dwExitCode);}
該函數適用于下面情形:編寫一個DLL,當它被初次映射到進程的地址空間中,該DLL便創建一個線程,當所創建的線程完成操作時,通過調用FreeLibrary函數從進程的地址空間卸載DLL,并且終止運行,然后立即調用ExitThread,如果線程分開調用FreeLibrary和ExitThread,就會出現問題,問題是調用FreeLibrary會立即從進程的地址空間中卸載DLL,返回時,包含對ExitThread調用的代碼就不可以使用,因此線程將無法執行任何代碼,導致訪問違規,終止整個進程。如果線程調用FreeLibraryAndExitThread,函數調用FreeLibrary使DLL被卸載,而下步執行指令是在Kernel32.dll中(不是剛被卸載的DLL),意味著線程能夠繼續執行下去調用ExitThread終止線程且不返回。
顯示鏈接到一個輸出符號:
一旦DLL模塊被顯示加載,線程就要獲取引用的符號的地址,方法是:
?????? FARPROC GetProcAddress(
???????????????????????????????????????????????? HINSTANCE hinstDll,
???????????????? ???????????PCSTR pszSymbolName);
如:FARPROC pfn=GetProcAddress(hinstDll,”someFunInDll”);
參數pszSymbolName的原型是PCSTR而非PCTSTR,因此只接受ANSI字符串而不能將Unicode字符串傳遞給該函數,因為編譯器和鏈接程序總是將符號名作為ANSI字符串存儲在DLL的輸出節中,參數pszSymbolName也可以指明想要的地址的符號的序號,如:
?????? FARPROC pfn=GetProcAddress(hinstDll,MAKEINTRESOURCE(2));
假設知道調用的函數序號是被賦予了2的值。
DLL的進入點函數:
一個DLL擁有單個進入點函數:
?????? BOOL WINAPI DllMain(HINSTANCE hinstDll,DWORD fdwReason,PVOID fImpLoad){
????????????? switch(fdwReason){
??????? ????????????? case DLL_PROCESS_ATTACH:
????????????????????????????????????????? break;
??????????????? case DLL_THREAD_ATTACH:
??????????????????????? break;
??????????????? case DLL_THREAD_DETACH:
??????????????????????? break;
??????????????? case DLL_PROCESS_DETACH:
??????????????????????? break;
????????? }
????????? Return TRUE;
???? }
1)DLL_PROCESS_ATTACH通知
當DLL被初次映射到進程的地址空間時,系統將調用該DLL的DllMain函數,傳遞fdwReason的值即為DLL_PROCESS_ATTACH。
2)DLL_PROCESS_DETACH通知
DLl從進程的地址空間中被卸載時,系統將調用DLL的DllMain函數,fdwReason即傳遞該值。
3)DLL_THREAD_ATTACH通知
當在一個進程中創建線程時,系統要查看當前映射到該進程的地址空間中的所有DLL文件映像,并調用每個文件映像帶有DLL_THREAD_ATTACH值DllMain函數,告訴所有的DLL執行每個線程的初始化操作,新創建的線程負責執行DLL的所有DllMain函數中的代碼,只有當所有的DLL都有機會處理該通知時,系統才運行新線程開始執行其線程函數。
4)DLL_THREAD_DETACH通知
線程終止的首選是使其線程函數返回,調用ExitThread函數告訴系統,線程要終止運行,但系統并不立即撤消,相反系統取出即將被撤消的線程,并讓它調用已經映射的DLL的所有帶有DLL_THREAD_DETACH值DllMain函數,通知所有的DLL執行每個線程的清除操作。
DllMain與C/C++運行期庫:
如使用Microsoft的Visual C++編譯器來創建DLL,需要得到C/C++運行期庫的初始幫助,如創建的DLL中包含一個全局變量,而這個全局變量是個C++類的實例,在DllMain函數使用該全局變量之前,需調用其構造函數,這個工作由C/C++運行期庫的DLL啟動代碼來完成。當鏈接DLL時,鏈接程序將DLL的進入點函數嵌入產生的DLL文件映像,使用鏈接程序的/ENTRY開關來設定該函數的地址。按照默認設置,使用Microsoft的鏈接程序并設定/DLL開關時,鏈接程序假設進入點函數稱為_DllMainCRTStartup,該函數包含在C/C++運行期的庫文件中,并且在鏈接DLL時被靜態鏈接到DLL的文件映像中。當DLL文件映像被映射到進程的地址空間中時,系統實際先調用_DllMainCRTStartup函數,而不是調用DllMain函數。_DllMainCRTStartup函數負責對C/C++運行期庫進行初始化,并且確保_DllMainCRTStartup收到DLL_PROCESS_ATTACH通知時創建任何全局或靜態C++對象,當執行任何C/C++運行期初始化時,_DllMainCRTStartup函數將調用DllMain函數。當DLL調用DLL_PROCESS_DETACH通知時,系統再次調用_DllMainCRTStartup函數,該函數即調用DllMain函數,當DllMain返回時,_DllMainCRTStartup就為DLL中的任何全局或靜態C++對象調用析構函數。
延遲加載DLL:
延遲加載DLL是個隱含連接的DLL,是等到代碼引用DLL中包含的一個符號時才進行加載,對下列情況比較有用:
1)應用程序使用若干DLL,初始化時間就比較長,此時在進程運行時分開加載各個DLL,則不需要一次將所有DLL映射到進程地址空間中;
2)如果調用代碼中的一個新函數,在無該函數的舊版本系統上運行,加載程序會報告錯誤,并且不允許應用程序運行。延遲加載可以先讓應用程序運行,運行時發現新函數在舊版本系統上無法運行,則不調用該函數。
延遲加載都像平常一樣創建DLL模塊及其可執行模塊,但需修改兩個鏈接程序開關,并且重新鏈接可執行模塊:
?????? /Lib:DelayImp.lib
?????? /DelayLoad:MyDll.dll
Lib開關告訴鏈接程序將一個特殊函數delayLoadHelper嵌入可執行模塊,DelayLoad開關則告訴鏈接程序:
1)從可執行模塊的輸入節中刪除MyDll.dll,當進程初始化時,操作系統的加載程序就不會顯示加載DLL;
2)將新的Delay Import(延遲輸入)節,也稱為.tidata嵌入可執行模塊,以指明那些函數正在從MyDll.dll中輸入;
3)通過轉移到對delayLoadHelper函數的調用,轉換到對延遲加載函數的調用;
若要卸載延遲加載的DLL,首先創建可執行文件時,設定另一個鏈接開關程序/delay:unload,其次,修改源代碼,在想要卸載DLL是調用:
?????? BOOL __FunLoadDelayLoadedDLL(PCSTR szDll);
鏈接程序開關/delay:unload該素鏈接程序將另一個節放入文件中,該節包含了清除已經調用的函數時需要的信息,可以再次調用delayLoadHelper函數。當調用__FunLoadDelayLoadedDLL時,想要卸載的延遲加載的DLL名字傳遞,該函數進入文件中的未卸載節,并清除DLL的所有函數地址,然后__FunLoadDelayLoadedDLL調用FreeLibrary卸載該DLL。注意,不能自己調用FreeLibrary來卸載DLL,否則函數的地址將不會被清除,這樣,當下次調用DLL中的函數時,會導致訪問違規。注意二,調用__FunLoadDelayLoadedDLL時,傳遞的DLL名字不應該包含路徑,名字中的字母應和將DLL名字傳遞給/delayload鏈接程序開關時使用的字母大小寫相同。注意三,如果不打算卸載延遲加載的DLL,則不要設置/delay:unload鏈接程序開關,且可執行文件的長度應該較小。
?
總結
以上是生活随笔為你收集整理的DLL的高级操作技术——Windows核心编程学习手札之二十的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: DLL基础——Windows核心编程学习
- 下一篇: 线程本地存储器——Windows核心编程