動態鏈接庫學習筆記

  1. 靜態鏈接庫與動態鏈接庫:lib(靜態鏈接庫)與DLL(動態鏈接庫)採用的都是共享代碼的方式,當我們引用了lib,那麼lib中的指令會被直接包含在最終生成的exe文件中,而我們若使用DLL,該DLL不必被包含在最終DLL文件中,exe文件執行時可以“動態”地引用和卸載這個DLL文件,並且lib中不可包含其他lib和DLL,而DLL中仍可包含其他的DLL和lib。
  2. 常用DLL文件:Kernel32.DLL:是windows中非常重要的32位動態鏈接庫文件,屬於內核級文件,它控制着系統的內存管理、數據的輸入輸出操作和中斷處理以及進程調度等;user32.DLL中的函數主要控制用戶界面;gdi32.DLL中的函數則負責圖形方面的操作。
  3. 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(在進行函數調用時,這個序號將發揮其作用)	
add@1

 

 

 

 

 

 

 

 

.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被映射到進程的地址空間時,系統調用該DLLDllMain函數,傳遞的fdwReason參數爲DLL_PROCESS_ATTACH,這種調用只會發生在第一次映射時,如果同一個進程後來爲已經映射進來的DLL在此調用LoadLibrary或LoadLibraryEx,操作系統只會增加DLL的使用次數。

 

·DLL_PROCESS_DETACH:表示動態鏈接庫即將被卸載,程序可以在這裏進行一些資源的釋放工作,如釋放內存、關閉文件等。什麼時候DLL會從進程的地址空間解除映射:

  1. FreeLibrary解除DLL映射,有幾個LoadLibrary,就要有幾個FreeLibrary。
  2. 進程結束而解除DLL映射,如果進程的終結是因爲調用了TerminateProcess,系統就不會使用DLL_PROCESS_DETACH來調用DLLDllMain函數,這就意味着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中的全局變量本身而不是其地址了,個人認爲這個方式最爲方便簡單。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章