當進程中的線程不再需要DLL中的引用符號時,可以從進程的地址空間中顯式卸載DLL,方法是調用下面的函數:
BOOL FreeLibrary(HINSTANCE hinstDll);
必須傳遞HINSTANCE值,以便標識要卸載的DLL。該值是較早的時候調用LoadLibrary(Ex)而返回的值。
也可以通過調用下面的函數從進程的地址空間中卸載DLL:
VOID FreeLibraryAndExitThread(
HINSTANCE hinstDll,
DWORD dwExitCode);
該函數是在Kernel32.dll中實現的,如下所示:
VOID FreeLibraryAndExitThread(HINSTANCE hinstDll,DWORD dwExitCode) {
FreeLibrary(hinstDll);
ExitThread(dwExitCode);
}
初看起來,這並不是個非常高明的代碼,你可能不明白,爲什麼Microsoft要創建FreeLibraryAndExitThread這個函數。其原因與下面的情況有關:假定你要編寫一個DLL,當它被初次映射到進程的地址空間中時,該DLL就創建一個線程。當該線程完成它的操作時,它通過調用FreeLibrary函數,從進程的地址空間中卸載該DLL,並且終止運行,然後立即調用ExitThread。
但是,如果線程分開調用FreeLibrary和ExitThread,就會出現一個嚴重的問題。這個問題是調用FreeLibrary會立即從進程的地址空間中卸載DLL。當調用的FreeLibrary返回時,包含對ExitThread調用的代碼就不再可以使用,因此線程將無法執行任何代碼。這將導致訪問違規,同時整個進程終止運行。
但是,如果線程調用FreeLibraryAndExitThread,該函數調用FreeLibrary,使DLL立即被卸載。下一個執行的指令是在Kernel32.dll中,而不是在剛剛被卸載的DLL中。這意味着該線程能夠繼續執行,並且可以調用ExitThread。ExitThread使該線程終止運行並且不返回。
一般來說,並沒有很大的必要去調用FreeLibraryAndExitThread函數。我曾經使用過一次,因爲我執行了一個非常特殊的任務。另外,我爲MicrosoftWindows3.1編寫了一個代碼,它並沒有提供這個函數。因此我高興地看到Microsoft將這個函數增加到了較新的Windows版本中。
在實際環境中,LoadLibrary和LoadLibraryEx這兩個函數用於對與特定的庫相關的進程使用計數進行遞增,FreeLibrary和FreeLibraryAndExitThread這兩個函數則用於對庫的每個進程的使用計數進行遞減。例如,當第一次調用LoadLibrary函數來加載DLL時,系統將DLL的文件映像映射到調用進程的地址空間中,並將DLL的使用計數設置爲1。如果同一個進程中的線程後來調用LoadLibrary來加載同一個DLL文件映像,系統並不第二次將DLL映像文件映射到進程的地址空間中,它只是將與該進程的DLL相關的使用計數遞增1。
爲了從進程的地址空間中卸載DLL文件映像,進程中的線程必須兩次調用FreeLibrary函數。第一次調用只是將DLL的使用計數遞減爲1,第二次調用則將DLL的使用計數遞減爲0。當系統發現DLL的使用計數遞減爲0時,它就從進程的地址空間中卸載DLL的文件映像。試圖調用DLL中的函數的任何線程都會產生訪問違規,因爲特定地址上的代碼不再被映射到進程的地址空間中。
系統爲每個進程維護了一個DLL的使用計數,也就是說,如果進程A中的一個線程調用下面的函數,然後進程B中的一個線程調用相同的函數,那麼MyLib.dll將被映射到兩個進程的地址空間中,這樣,進程A和進程B的DLL使用計數都將是1。
HINSTANCE hinstDll = LoadLibrary("MyLib.dll");
如果進程B中的線程後來調用下面的函數,那麼進程B的DLL使用計數將變成0,並且該DLL將從進程B的地址空間中卸載。但是,進程A的地址空間中的DLL映射不會受到影響,進程A的DLL使用計數仍然是1。
FreeLibrary(hinstDll);
如果調用GetModuleHandle函數,線程就能夠確定DLL是否已經被映射到進程的地址空間中:
HINSTANCE GetModuleHandle(PCTSTR pszModuleName);
例如,只有當MyLib.dll尚未被映射到進程的地址空間中時,下面這個代碼才能加載該文件:
HINSTANCE hinstDll = GetModuleHandle("MyLib");//DLL externsion assumed
if(hinstDll == NULL) {
hinstDll = LoadLibrary("MyLib");//DLL externsion assumed
}
如果只有DLL的HINSTANCE值,那麼可以調用GetModuleFileName函數,確定DLL(或.exe)的全路徑名:
DWORD GetModulefileName(
HINSTANCE hinstModule,
PTSTR pszPathName,
DWORD cchPath);
第一個參數是DLL(或.exe)的HINSTANCE。第二個參數pszPathName是該函數將文件映像的全路徑名放入的緩存的地址。第三參數cchPath用於設定緩存的大小(以字符爲計量單位)。