定義:運行時庫 靜態庫 動態庫
運行時庫:Unix中一個典型的運行時庫例子就是libc,它包含標準的C函數,如,print(),exit()等等,用戶能創建他們自己的運行庫(在Windows中是DLL),而具體的細節依賴編譯器和操作系統的。
靜態庫:函數和數據被編譯進一個二進制文件(通常擴展名為.lib),靜態庫實際上是在鏈接時被鏈接到EXE的,庫本身不需要與可執行文件一起發行。
動態庫:用VC++創建的動態庫包含兩個文件,一個lib文件和一個dll文件,這個lib文件就是引入庫,不是靜態庫,引入庫有時也叫輸入庫或導入庫。
注:windows操作系統下動態庫和運行時庫的擴展名都是.dll,COM組件的擴展名也是.dll,動態庫的引入庫和靜態庫的擴展名都是.lib。
靜態庫的特點和創建過程
靜態庫之所以稱為【靜態庫】,是因為在鏈接階段,會將匯編生成的目標文件.o與引用到的庫一起鏈接打包到可執行文件中。因此對應的鏈接方式稱為靜態鏈接。
試想一下,靜態庫與匯編生成的目標文件一起鏈接為可執行文件,那么靜態庫必定跟.o文件格式相似。其實一個靜態庫可以簡單看成是一組目標文件(.o/.obj文件)的集合,即很多目標文件經過壓縮打包后形成的一個文件。靜態庫特點總結如下:
靜態庫對函數庫的鏈接是放在編譯時期完成的。
程序在運行時與函數庫再無瓜葛,移植方便。
浪費空間和資源,因為所有相關的目標文件與牽涉到的函數庫被鏈接合成一個可執行文件。
如下圖是靜態庫創建過程:
動態庫的特點和創建過程
為什么還需要動態庫?
為什么還需要動態庫,其實也就是靜態庫的特點導致。
空間浪費是靜態庫的一個問題。
另一個問題是靜態庫對程序的更新、部署和發布頁會帶來麻煩。如果靜態庫libxx.lib更新了,所有使用它的應用程序都需要重新編譯、發布給用戶(對于玩家來說,只是一個很小的改動,卻導致整個程序重新下載,全量更新)。
動態庫在程序編譯時并不會被連接到目標代碼中,而是在程序運行是才被載入。不同的應用程序如果調用相同的庫,那么在內存里只需要有一份該共享庫的實例,規避了空間浪費問題。動態庫在程序運行時才被載入,也解決了靜態庫對程序的更新、部署和發布頁會帶來麻煩。用戶只需要更新動態庫即可,增量更新。
動態庫特點總結:
動態庫把對一些庫函數的鏈接載入推遲到程序運行的時期。
可以實現進程之間的資源共享。(因此動態庫也稱為共享庫)
將一些程序升級變得簡單。
甚至可以真正做到鏈接載入完全由程序員在程序代碼中控制(顯示調用)。
?
Windows與Linux執行文件格式不同,在創建動態庫的時候有一些差異。
在Windows系統下的執行文件格式是PE格式,動態庫需要一個DllMain函數做初始化的入口,通常在導出函數的聲明時需要有_declspec(dllexport)關鍵字。
Linux下gcc編譯的執行文件默認是ELF格式,不需要初始化入口,亦不需要函數做特別的聲明,編寫比較方便。
windows下調用動態庫的方法:
1 隱式加載:即在程序中包含lib文件和.h文件,隱式鏈接有時稱為靜態加載或加載時動態鏈接。例如:
#include "somedll.h"
#pragma comment( lib, "somedll.lib")
然后就可以直接調用此dll中的函數,注意運行時仍然需要somedll.dll。
2 顯示加載:使用loadlibrary,GetProcAddress,FreeLibrary,不需要.h文件和.lib文件,但是要知道函數的原型。顯式鏈接有時稱為動態加載或運行時動態鏈接。
3 區別:如果在進程啟動時未找到 DLL,操作系統將終止使用隱式鏈接的進程。同樣是在此情況下,使用顯式鏈接的進程則不會被終止,并可以嘗試從錯誤中恢復。
有關Win32 DLL,Unix共享庫及普通庫的詳細庫結構信息請參考《鏈接器與加載器》一書。
鏈接庫的鏈接方式
1 確定要使用的鏈接方法:
有兩種類型的鏈接:隱式鏈接和顯式鏈接。
隱式鏈接
應用程序的代碼調用導出 DLL 函數時發生隱式鏈接。當調用可執行文件的源代碼被編譯或被匯編時,DLL 函數調用在對象代碼中生成一個外部函數引用。若要解析此外部引用,應用程序必須與 DLL 的創建者所提供的導入庫(.LIB 文件)鏈接。
導入庫僅包含加載 DLL 的代碼和實現 DLL 函數調用的代碼。在導入庫中找到外部函數后,會通知鏈接器此函數的代碼在DLL 中。要解析對 DLL 的外部引用,鏈接器只需向可執行文件中添加信息,通知系統在進程啟動時應在何處查找 DLL 代碼。
系統啟動包含動態鏈接引用的程序時,它使用程序的可執行文件中的信息定位所需的 DLL。如果系統無法定位 DLL,它將終止進程并顯示一個對話框來報告錯誤。否則,系統將 DLL 模塊映射到進程的地址空間中。
如果任何 DLL 具有(用于初始化代碼和終止代碼的)入口點函數,操作系統將調用此函數。在傳遞到入口點函數的參數中,有一個指定用以指示 DLL 正在附帶到進程的代碼。如果入口點函數沒有返回 TRUE,系統將終止進程并報告錯誤。最后,系統修改進程的可執行代碼以提供 DLL 函數的起始地址。
與程序代碼的其余部分一樣,DLL 代碼在進程啟動時映射到進程的地址空間中,且僅當需要時才加載到內存中。因此,由 .def 文件用來在 Windows 的早期版本中控制加載的 PRELOAD 和 LOADONCALL 代碼屬性不再具有任何意義。
顯式鏈接
大部分應用程序使用隱式鏈接,因為這是最易于使用的鏈接方法。但是有時也需要顯式鏈接。下面是一些使用顯式鏈接的常見原因:
直到運行時,應用程序才知道需要加載的 DLL 的名稱。例如,應用程序可能需要從配置文件獲取 DLL 的名稱和導出函數名。
如果在進程啟動時未找到 DLL,操作系統將終止使用隱式鏈接的進程。同樣是在此情況下,使用顯式鏈接的進程則不會被終止,并可以嘗試從錯誤中恢復。例如,進程可通知用戶所發生的錯誤,并讓用戶指定 DLL 的其他路徑。如果使用隱式鏈接的進程所鏈接到的 DLL 中有任何 DLL 具有失敗的 DllMain 函數,該進程也會被終止。同樣是在此情況下,使用顯式鏈接的進程則不會被終止。
因為Windows 在應用程序加載時加載所有的 DLL,故隱式鏈接到許多 DLL 的應用程序啟動起來會比較慢。為提高啟動性能,應用程序可隱式鏈接到那些加載后立即需要的 DLL,并等到在需要時顯式鏈接到其他 DLL。
顯式鏈接下不需將應用程序與導入庫鏈接。如果 DLL 中的更改導致導出序號更改,使用顯式鏈接的應用程序不需重新鏈接(假設它們是用函數名而不是序號值調用 GetProcAddress),而使用隱式鏈接的應用程序必須重新鏈接到新的導入庫。
下面是需要注意的顯式鏈接的兩個缺點:
???????????? 1.?如果 DLL 具有 DllMain 入口點函數,則操作系統在調用 LoadLibrary?的線程上下文中調用此函數。如果由于以前調用了LoadLibrary 但沒有相應地調用 FreeLibrary 函數而導致 DLL 已經附加到進程,則不會調用此入口點函數。如果 DLL 使用 DllMain 函數為進程的每個線程執行初始化,顯式鏈接會造成問題,因為調用 LoadLibrary(或AfxLoadLibrary)時存在的線程將不會初始化。服務器負載高,性能下降,導致無法及時的處理客戶端的請求,可能是服務器硬件本身需要升級,另外一方面是程序自身的bug導致的吞吐量不夠,性能低、還有就是可能是架構問題,比如沒有分布式處理,無法動態擴容,基本上你需要查看內存,CPU,磁盤使用情況,使用top,free ,df等命令來動態查看找到異常指標的進程。
???????????? 2.?如果DLL 將靜態作用域數據聲明為 __declspec(thread),則在顯式鏈接時 DLL會導致保護錯誤。用 LoadLibrary 加載 DLL 后,每當代碼引用此數據時 DLL 就會導致保護錯誤。(靜態作用域數據既包括全局靜態項,也包括局部靜態項。)因此,創建DLL 時應避免使用線程本地存儲區,或者應(在用戶嘗試動態加載時)告訴 DLL 用戶潛在的缺陷。