徹底搞懂動態(tài)庫、靜態(tài)庫、運行時庫、引入庫之間的區(qū)別
定義
- 運行時庫:Unix中一個典型的運行時庫例子就是libc,它包含標準的C函數(shù),如,print(),exit()等等,用戶能創(chuàng)建他們自己的運行庫(在Windows中是DLL),而具體的細節(jié)依賴編譯器和操作系統(tǒng)的。
- 靜態(tài)庫:函數(shù)和數(shù)據(jù)被編譯進一個二進制文件(通常擴展名為.lib),靜態(tài)庫實際上是在鏈接時被鏈接到EXE的,庫本身不需要與可執(zhí)行文件一起發(fā)行。
- 動態(tài)庫:用VC++創(chuàng)建的動態(tài)庫包含兩個文件,一個lib文件和一個dll文件,這個lib文件就是引入庫,不是靜態(tài)庫,引入庫有時也叫輸入庫或導入庫。
注:windows操作系統(tǒng)下動態(tài)庫和運行時庫的擴展名都是.dll,COM組件的擴展名也是.dll,動態(tài)庫的引入庫和靜態(tài)庫的擴展名都是.lib。
windows下調用動態(tài)庫的方法:
(1) 隱式加載:即在程序中包含lib文件和.h文件,隱式鏈接有時稱為靜態(tài)加載或加載時動態(tài)鏈接。例如:
- #include "somedll.h"
- #pragma comment( lib, "somedll.lib")
然后就可以直接調用此dll中的函數(shù),注意運行時仍然需要somedll.dll。
(2) 顯示加載:使用loadlibrary,GetProcAddress,FreeLibrary,不需要.h文件和.lib文件,但是要知道函數(shù)的原型。顯式鏈接有時稱為動態(tài)加載或運行時動態(tài)鏈接。
(3) 區(qū)別:如果在進程啟動時未找到 DLL,操作系統(tǒng)將終止使用隱式鏈接的進程。同樣是在此情況下,使用顯式鏈接的進程則不會被終止,并可以嘗試從錯誤中恢復。
確定要使用的鏈接方法:
有兩種類型的鏈接:隱式鏈接和顯式鏈接。
1. 隱式鏈接
- 應用程序的代碼調用導出 DLL 函數(shù)時發(fā)生隱式鏈接。當調用可執(zhí)行文件的源代碼被編譯或被匯編時,DLL 函數(shù)調用在對象代碼中生成一個外部函數(shù)引用。若要解析此外部引用,應用程序必須與 DLL 的創(chuàng)建者所提供的導入庫(.LIB 文件)鏈接。
- 導入庫僅包含加載 DLL 的代碼和實現(xiàn) DLL 函數(shù)調用的代碼。在導入庫中找到外部函數(shù)后,會通知鏈接器此函數(shù)的代碼在DLL 中。要解析對 DLL 的外部引用,鏈接器只需向可執(zhí)行文件中添加信息,通知系統(tǒng)在進程啟動時應在何處查找 DLL 代碼。
- 系統(tǒng)啟動包含動態(tài)鏈接引用的程序時,它使用程序的可執(zhí)行文件中的信息定位所需的 DLL。如果系統(tǒng)無法定位 DLL,它將終止進程并顯示一個對話框來報告錯誤。否則,系統(tǒng)將 DLL 模塊映射到進程的地址空間中。
- 如果任何 DLL 具有(用于初始化代碼和終止代碼的)入口點函數(shù),操作系統(tǒng)將調用此函數(shù)。在傳遞到入口點函數(shù)的參數(shù)中,有一個指定用以指示 DLL 正在附帶到進程的代碼。如果入口點函數(shù)沒有返回 TRUE,系統(tǒng)將終止進程并報告錯誤。最后,系統(tǒng)修改進程的可執(zhí)行代碼以提供 DLL 函數(shù)的起始地址。
- 與程序代碼的其余部分一樣,DLL 代碼在進程啟動時映射到進程的地址空間中,且僅當需要時才加載到內存中。因此,由 .def 文件用來在 Windows 的早期版本中控制加載的 PRELOAD 和 LOADONCALL 代碼屬性不再具有任何意義。
2. 顯式鏈接
大部分應用程序使用隱式鏈接,因為這是最易于使用的鏈接方法。但是有時也需要顯式鏈接。下面是一些使用顯式鏈接的常見原因:
- 直到運行時,應用程序才知道需要加載的 DLL 的名稱。例如,應用程序可能需要從配置文件獲取 DLL 的名稱和導出函數(shù)名。
- 如果在進程啟動時未找到 DLL,操作系統(tǒng)將終止使用隱式鏈接的進程。同樣是在此情況下,使用顯式鏈接的進程則不會被終止,并可以嘗試從錯誤中恢復。例如,進程可通知用戶所發(fā)生的錯誤,并讓用戶指定 DLL 的其他路徑。如果使用隱式鏈接的進程所鏈接到的 DLL 中有任何 DLL 具有失敗的 DllMain 函數(shù),該進程也會被終止。同樣是在此情況下,使用顯式鏈接的進程則不會被終止。
- 因為Windows 在應用程序加載時加載所有的 DLL,故隱式鏈接到許多 DLL 的應用程序啟動起來會比較慢。為提高啟動性能,應用程序可隱式鏈接到那些加載后立即需要的 DLL,并等到在需要時顯式鏈接到其他 DLL。
- 顯式鏈接下不需將應用程序與導入庫鏈接。如果 DLL 中的更改導致導出序號更改,使用顯式鏈接的應用程序不需重新鏈接(假設它們是用函數(shù)名而不是序號值調用 GetProcAddress),而使用隱式鏈接的應用程序必須重新鏈接到新的導入庫。
下面是需要注意的顯式鏈接的兩個缺點:
- 如果 DLL 具有 DllMain 入口點函數(shù),則操作系統(tǒng)在調用 LoadLibrary 的線程上下文中調用此函數(shù)。如果由于以前調用了LoadLibrary 但沒有相應地調用 FreeLibrary 函數(shù)而導致 DLL 已經(jīng)附加到進程,則不會調用此入口點函數(shù)。如果 DLL 使用 DllMain 函數(shù)為進程的每個線程執(zhí)行初始化,顯式鏈接會造成問題,因為調用 LoadLibrary(或 AfxLoadLibrary)時存在的線程將不會初始化。
- 如果 DLL 將靜態(tài)作用域數(shù)據(jù)聲明為 __declspec(thread),則在顯式鏈接時 DLL 會導致保護錯誤。用 LoadLibrary 加載 DLL 后,每當代碼引用此數(shù)據(jù)時 DLL 就會導致保護錯誤。(靜態(tài)作用域數(shù)據(jù)既包括全局靜態(tài)項,也包括局部靜態(tài)項。)因此,創(chuàng)建 DLL 時應避免使用線程本地存儲區(qū),或者應(在用戶嘗試動態(tài)加載時)告訴 DLL 用戶潛在的缺陷。
3. 隱式鏈接:
為隱式鏈接到 DLL,可執(zhí)行文件必須從 DLL 的提供程序獲取下列各項:
- 包含導出函數(shù)和/或 C++ 類的聲明的頭文件(.h 文件)。類、函數(shù)和數(shù)據(jù)均應具有 __declspec(dllimport),有關更多信息,請參見 dllexport, dllimport。
- 要鏈接的導入庫(.LIB files)。(生成 DLL 時鏈接器創(chuàng)建導入庫。)
- 實際的 DLL(.dll 文件)。
使用 DLL 的可執(zhí)行文件必須包括頭文件,此頭文件包含每個源文件中的導出函數(shù)(或 C++ 類),而這些源文件包含對導出函數(shù)的調用。從編碼的角度講,導出函數(shù)的函數(shù)調用與任何其他函數(shù)調用一樣。
若要生成調用可執(zhí)行文件,必須與導入庫鏈接。如果使用的是外部生成文件,請指定導入庫的文件名,此導入庫中列出了要鏈接到的其他對象 (.obj) 文件或庫。
操作系統(tǒng)在加載調用可執(zhí)行文件時,必須能夠定位 DLL 文件。
4. 顯式鏈接:
在顯式鏈接下,應用程序必須進行函數(shù)調用以在運行時顯式加載 DLL。為顯式鏈接到 DLL,應用程序必須:
- 調用 LoadLibrary(或相似的函數(shù))以加載 DLL 和獲取模塊句柄。
- 調用 GetProcAddress,以獲取指向應用程序要調用的每個導出函數(shù)的函數(shù)指針。由于應用程序是通過指針調用 DLL 的函數(shù),編譯器不生成外部引用,故無需與導入庫鏈接。
- 使用完 DLL 后調用 FreeLibrary。