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