動態連結庫學習筆記
- 靜態連結庫與動態連結庫:lib(靜態連結庫)與DLL(動態連結庫)採用的都是共享程式碼的方式,當我們引用了lib,那麼lib中的指令會被直接包含在最終生成的exe檔案中,而我們若使用DLL,該DLL不必被包含在最終DLL檔案中,exe檔案執行時可以“動態”地引用和解除安裝這個DLL檔案,並且lib中不可包含其他lib和DLL,而DLL中仍可包含其他的DLL和lib。
- 常用DLL檔案:Kernel32.DLL:是windows中非常重要的32位動態連結庫檔案,屬於核心級檔案,它控制著系統的記憶體管理、資料的輸入輸出操作和中斷處理以及程序排程等;user32.DLL中的函式主要控制使用者介面;gdi32.DLL中的函式則負責圖形方面的操作。
- DLL優點:
- 使用較少的資源:當多個程式使用同一個函式庫時,DLL可以減少在磁碟和實體記憶體中載入的程式碼的重複量。這不僅可以大大影響在前臺執行的程式,而且可以影響到其他在Windows作業系統上執行的程式。
- 推廣模組式體系結構:DLL 有助於促進模組式程式的開發。這可以幫助您開發要求提供多個語言版本的大型程式或要求具有模組式體系結構的程式。
- 簡化部署和安裝:當 DLL 中的函式需要更新或修復時,部署和安裝 DLL 不要求重新建立程式與該 DLL 的連結。此外,如果多個程式使用同一個 DLL,那麼多個程式都將從該更新或修復中獲益。當您使用定期更新或修復的第三方 DLL 時,此問題可能會更頻繁地出現。
4.現代編譯器的主要工作流程:源程式(source code)->前處理器(preprocessor)->編譯器(compiler)->彙編程式(assembler)->目標程式(object)->聯結器(連結器,linker)->可執行程式(executables)
與靜態連結庫的關係:在我們使用#pragma comment(lib,”****\\debug\\***.lib”)以後,本檔案生成的obj檔案會與libTest.lib一起連線,在vs裡的附加依賴項裡新增lib檔案也是如此。
5.匯出函式的宣告:
- 在函式宣告前加上__declspec(dllexport);
- 採用模組定義(.def)檔案宣告,.def檔案為連結器提供了有關被連結程式的匯出、屬性及其他方面的資訊。
6. .def:
這段程式碼演示了怎樣用.def檔案將函式宣告為DLL匯出函式(需在工程中新增lib.def檔案)
;lib.def:匯出DLL函式
;LIBRARY語句說明def檔案相應的DLL
LIBRARY dllT
;後面列出要匯出函式的名稱
EXPORTS
;可以在def檔案中的匯出函式名後加@n,表示要匯出函式的序號為n(在進行函式呼叫時,這個序號將發揮其作用)
[email protected]
.def檔案中的註釋由每個逐行開始處的分號(;)指定,且註釋不能與語句共享一行
可以在命令列中通過dumpbin/exports *.DLL 命令檢視*.DLL檔案的匯出符號
7.DLL的呼叫方式:
- 動態呼叫:
由“LoadLibray-GetProcAddress-FreeLibrary”這樣的系統api提供的“DLL載入-DLL函式地址獲取-DLL釋放”方式被我們成為DLL的動態呼叫。
特點:可以完全由開發者決定何時載入和解除安裝DLL。
- 靜態呼叫:
靜態呼叫的進行需要完成兩個步驟:告訴編譯器與DLL對應的.lib檔案所在的路徑及檔名(#pragma comment(lib,”***.lib”));宣告匯入函式(__declspec(dllimport)***(***))。
特點:由編譯系統完成對DLL的載入和應用程式結束時DLL的解除安裝。當呼叫某DLL的應用程式結束時,若系統中還有其他程式使用該DLL,則windows對DLL的引用記錄減1,知道所有使用該DLL的程式都結束時才釋放它。靜態呼叫方式簡單實用,但不如動態呼叫方式靈活。
靜態呼叫方式不再使用系統API來載入、解除安裝DLL以及獲取DLL中匯出函式的地址。這是因為:當我們通過靜態連結方式編譯生成應用程式時,應用程式中呼叫的與.lib檔案中匯出符號相匹配的函式符號將進入到生成的exe 檔案中,.lib檔案中所包含的與之對應的DLL檔案的檔名也被編譯器儲存在 exe檔案內部。當應用程式執行過程中需要載入DLL檔案時,Windows將根據這些資訊發現並載入DLL,然後通過符號名實現對DLL函式的動態連結。這樣,exe將能直接通過函式名呼叫DLL的函式,就象呼叫程式內部的其他函式一樣。
DLL載入方式補充(道理一樣):
- 隱式載入:
隱式載入是程式載入記憶體時載入所需的DLL檔案,且該DLL隨主程序始終佔用記憶體。在編碼時需要使用#pragma comment(lib,”myDLL.lib”)獲得所需函式的入口。注意該.lib與靜態連結庫的.lib檔案不同,靜態連結庫的.lib中包含了所需函式的程式碼,動態連結庫的.lib僅指示函式在DLL檔案中的入口。
- 顯式載入:
顯示載入是在程式執行過程中載入,不需要該DLL時則將其釋放。在需要時使用loadLibrary載入,不需要時使用FreeLibrary釋放。如果在LoadLibrary時該DLL已經在記憶體,則只需將其引用計數加1,如果其引用計數減為0則移出記憶體。
8.DllMain函式:
DllMain函式是DLL程式的入口函式,這個函式是DLL的內部函式,這意味著不能在應用工程中引用DllMain函式。
DllMain函式的框架結構:
BOOL APIENTRY DllMain( HMODULE hModule, //本模組控制代碼
DWORD ul_reason_for_call, //呼叫的原因
LPVOID lpReserved //顯式載入與隱式載入
)
{
switch (ul_reason_for_call)
{
//動態連結庫剛被對映到某個程序的地址空間
case DLL_PROCESS_ATTACH:
//應用程式建立了一個新的執行緒
case DLL_THREAD_ATTACH:
//應用程式某個執行緒正常終止
case DLL_THREAD_DETACH:
//動態連結庫將被解除安裝
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
ul_reason_for_call引數的值表示本次呼叫的原因,可能是下面四種情況中的一種:
·DLL_PROCESS_ATTACH:表示動態連結庫剛被某個程序載入,程式可以在這裡做一些初始化工作,並返回TRUE表示初始化成功,返回FALSE表示初始化出錯。當DLL被對映到程序的地址空間時,系統呼叫該DLL的DllMain函式,傳遞的fdwReason引數為DLL_PROCESS_ATTACH,這種呼叫只會發生在第一次對映時,如果同一個程序後來為已經對映進來的DLL在此呼叫LoadLibrary或LoadLibraryEx,作業系統只會增加DLL的使用次數。
·DLL_PROCESS_DETACH:表示動態連結庫即將被解除安裝,程式可以在這裡進行一些資源的釋放工作,如釋放記憶體、關閉檔案等。什麼時候DLL會從程序的地址空間解除對映:
- FreeLibrary解除DLL對映,有幾個LoadLibrary,就要有幾個FreeLibrary。
- 程序結束而解除DLL對映,如果程序的終結是因為呼叫了TerminateProcess,系統就不會使用DLL_PROCESS_DETACH來呼叫DLL的DllMain函式,這就意味著DLL在程序結束前沒有機會執行任何清理工作。
注意:當用DLL_PROCESS_ATTACH呼叫DLL的DllMain函式時,如果返回FALSE,說明沒有初始化成功,系統仍會用DLL_PROCESS_DETACH呼叫DLL的DllMain函式。因此,必須確保清理那些沒有成功初始化的東西。
·DLL_THREAD_ATTACH:表示應用程式建立了一個新的執行緒,當程序建立一個執行緒時,系統檢視當前對映到程序地址空間中的所有DLL檔案映像,並用值DLL_THREAD__ATTACH呼叫DLL的DllMain函式。新建立的執行緒負責執行這次DLL的DllMain函式,只有當所有的DLL都處理完這一通知後,系統才允許程序開始執行它的執行緒函式。
注意:程序每次建立執行緒,都會用DLL_THREAD__ATTACH來初始化,哪怕是從執行緒中建立執行緒也是一樣。
·DLL_THREAD_DETACH:表示某個執行緒正常終止,如果執行緒呼叫了ExitThread來結束執行緒(執行緒函式返回時,系統也會自動呼叫ExitThread),系統檢視當前對映到程序空間中的所有DLL檔案映像,並用DLL_THREAD_DETACH來呼叫DllMain函式,通知所有的DLL去執行執行緒級的清理工作。
注意:如果執行緒的結束是因為系統中的一個執行緒呼叫了TerminateThread,系統就不會用值DLL_THREAD_DETACH來呼叫所有DLL的DllMain函式。
9.DLL匯出變數:
DLL定義的全域性變數可以被呼叫程序訪問;DLL也可以訪問呼叫程序的全域性資料。
若要匯出某全域性變數,我們需要往.def檔案的EXPORTS後新增 變數名 DATA ,在應用程式函式中使用 extern type funcName; 這條語句宣告匯入的不是DLL中的全域性變數本身,而是其地址,應用程式必須通過強制指標轉換來使用DLL中的全域性變數。例如使用時應:*(type*)funcName = *; 千萬不能不經過強制型別轉換就進行賦值,那樣會將這個變數的地址改變。
而如果是使用_declspec(dllimport)來進行匯入的話,使用變數的時候就不需要進行強制型別轉換,這個方式匯入的就是DLL中的全域性變數本身而不是其地址了,個人認為這個方式最為方便簡單。