目前以lib后綴的庫有兩種,一種為靜態鏈接庫(Static Libary,以下簡稱“靜態庫”),另一種為動態連接庫(DLL,以下簡稱“動態庫”)的導入庫(Import Libary,以下簡稱“導入庫”)。
靜態庫是一個或者多個obj文件的打包,所以有人干脆把從obj文件生成lib的過程稱為Archive,即合並到一起。比如你鏈接一個靜態庫,如果其中有錯,它會准確的找到是哪個obj有錯,即靜態lib只是殼子。
動態庫一般會有對應的導入庫,方便程序靜態載入動態鏈接庫,否則你可能就需要自己LoadLibary調入DLL文件,然后再手工GetProcAddress獲得對應函數了。有了導入庫,你只需要鏈接導入庫后按照頭文件函數接口的聲明調用函數就可以了。
導入庫和靜態庫的區別很大,他們實質是不一樣的東西。靜態庫本身就包含了實際執行代碼、符號表等等,而對於導入庫而言,其實際的執行代碼位於動態庫中,導入庫只包含了地址符號表等,確保程序找到對應函數的一些基本地址信息。
這也是實際上很多開源代碼發布的慣用方式:
1. 預編譯的開發包:包含一些.dll文件和一些.lib文件。其中這里的.lib就是導入庫,而不要錯以為是靜態庫。但是引入方式和靜態庫一樣,要在鏈接路 徑上添加找到這些.lib的路徑。而.dll則最好放到最后產生的應用程序exe執行文件相同的目錄。這樣運行時,就會自動調入動態鏈接庫。
2. 用戶自己編譯: 下載的是源代碼,按照readme自己編譯。生成很可能也是.dll + .lib(導入庫)的庫文件
3. 如果你只有dll,並且你知道dll中函數的函數原型,那么你可以直接在自己程序中使用LoadLibary調入DLL文件,GetProcAddress
DLL:
動態鏈接庫 (DLL) 是作為共享函數庫的可執行文件。動態鏈接提供了一種方法,使進程可以調用不屬於其可執行代碼的函數。函數的可執行代碼位於一個 DLL 中,該 DLL 包含一個或多個已被編譯、鏈接並與使用它們的進程分開存儲的函數。DLL 還有助於共享數據和資源。多個應用程序可同時訪問內存中單個 DLL 副本的內容。
動態鏈接與靜態鏈接的不同之處在於它允許可執行模塊(.dll 文件或 .exe 文件)僅包含在運行時定位 DLL 函數的可執行代碼所需的信息。在靜態鏈接中,鏈接器從靜態鏈接庫獲取所有被引用的函數,並將庫同代碼一起放到可執行文件中。
使用動態鏈接代替靜態鏈接有若干優點。DLL 節省內存,減少交換操作,節省磁盤空間,更易於升級,提供售后支持,提供擴展 MFC 庫類的機制,支持多語言程序,並使國際版本的創建輕松完成。
API 就是應用程序編程接口。它是能用來操作組件、應用程序或者操作系統的一組函數。典型的情況下,API 由一個或多個提供某種特殊功能的 DLL 組成。
DLL 是一個文件,其中包含了在 Microsoft? Windows? 下運行的任何應用程序都可調用的函數。運行時,DLL 中的函數動態地鏈接到調用它的應用程序中。無論有多少應用程序調用 DLL 中的某個函數,在磁盤上只有一個文件包含該函數,且只在它調入內存時才創建該 DLL。
您聽到最多的 API 可能是 Windows API,它包括構成 Windows 操作系統的各種 DLL。每個 Windows 應用程序都直接或間接地與 Windows API 互動。Windows API 保證 Windows 下運行的所有應用程序的行為方式一致。
注意 隨着 Windows 操作系統的發展,現已發布了幾個版本的 Windows API。Windows 3.1 使用 Win16 API。Microsoft? Windows NT?、Windows 95 和 Windows 98 平台使用 Microsoft? Win32? API。
除 Windows API 外,其他一些 API 也已發布。例如,郵件應用程序編程接口 (MAPI) 是一組可用於編寫電子郵件應用程序的 DLL。
API 傳統上是為開發 Windows 應用程序的 C 和 C++ 程序員編寫的,但其他的編程語言(包括VBA)也可以調用 DLL 中的函數。因為大部分 DLL 主要是為 C 和 C++ 程序員編寫和整理說明的,所以調用 DLL 函數的方法與調用 VBA 函數會有所不同。在使用 API 時必須了解如何給 DLL 函數傳遞參數。
警告 調用 Windows API 和 其他 DLL 函數可能會給您的應用程序帶來不良影響。從自己的代碼中直接調用 DLL 函數時,您繞過了 VBA 通常提供的一些安全機制。如果在定義或調用 DLL 函數時出現錯誤(所有程序員都不可避免),可能會在應用程序中引起應用程序錯誤(也稱為通用性保護錯誤,或 GPF)。最好的解決辦法是在運行代碼以前保存該項目,並確保了解 DLL 函數調用的原理。
LIB 創建標准庫、導入庫和導出文件,在生成 32 位程序時可將它們與 LINK 一起使用。LIB 從命令提示運行。
可在下列幾種模式下使用 LIB:
生成或修改 COFF 庫
將成員對象提取到文件中
創建導出文件和導入庫
這些模式是互斥的;每次只能以一種模式使用 LIB。
1 靜態鏈接庫的優點
(1) 代碼裝載速度快,執行速度略比動態鏈接庫快;
(2) 只需保證在開發者的計算機中有正確的.LIB文件,在以二進制形式發布程序時不需考慮在用戶的計算機上.LIB文件是否存在及版本問題,可避免DLL地獄等問題。
2 動態鏈接庫的優點
(1) 更加節省內存並減少頁面交換;
(2) DLL文件與EXE文件獨立,只要輸出接口不變(即名稱、參數、返回值類型和調用約定不變),更換DLL文件不會對EXE文件造成任何影響,因而極大地提高了可維護性和可擴展性;
(3) 不同編程語言編寫的程序只要按照函數調用約定就可以調用同一個DLL函數;
(4)適用於大規模的軟件開發,使開發過程獨立、耦合度小,便於不同開發者和開發組織之間進行開發和測試。
3 不足之處
(1) 使用靜態鏈接生成的可執行文件體積較大,包含相同的公共代碼,造成浪費;
(2) 使用動態鏈接庫的應用程序不是自完備的,它依賴的DLL模塊也要存在,如果使用載入時動態鏈接,程序啟動時發現DLL不存在,系統將終止程序並給出錯誤信息。而使用運行時動態鏈接,系統不會終止,但由於DLL中的導出函數不可用,程序會加載失敗;速度比靜態鏈接慢。當某個模塊更新后,如果新模塊與舊的模塊不兼容,那么那些需要該模塊才能運行的軟件,統統撕掉。這在早期Windows中很常見。
Linux下的動態鏈接庫和靜態鏈接庫
動態庫和靜態庫是做什么用的呢?鏈接的時候,使用的到底是動態庫,還是靜態庫呢?
Linux中有兩類函數庫,分別是靜態庫和動態庫。
靜態函數庫:
這類庫的名字一般是libxxx.a;利用靜態函數庫編譯成的文件比較大,因為整個函數庫的所有數據都會被整合進目標代碼中,他的優點就顯而易見了,即編譯后的執行程序不需要外部的函數庫支持,因為所有使用的函數都已經被編譯進去了。當然這也會成為他的缺點,因為如果靜態函數庫改變了,那么你的程序必須重新編譯。
動態函數庫:
這類庫的名字一般是libxxx.so;相對於靜態函數庫,動態函數庫在編譯的時候並沒有被編譯進目標代碼中,你的程序執行到相關函數時才調用該函數庫里的相應函數,因此動態函數庫所產生的可執行文件比較小。由於函數庫沒有被整合進你的程序,而是程序運行時動態的申請並調用,所以程序的運行環境中必須提供相應的庫。動態函數庫的改變並不影響你的程序,所以動態函數庫的升級比較方便。
在Linux下編譯程序的時候,我們通常使用-l選項來指明要鏈接的庫,編譯完了以后,使用 ldd命令檢查發現程序鏈接的是動態庫,原來,使用-l選項的時候,連接器在支持動態鏈接的系統上會優先搜索可用的動態庫,如果有,則鏈接動態庫,如果沒有動態庫,才會鏈接靜態庫。大家可以把/usr/lib下某個庫的動態版本都移掉,之后,再編譯程序,再使用ldd就會發現,不再那個動態庫了。(如果在移掉動態庫以后,編譯的時候,報找不到dlopen之類的鏈接錯誤,需要把-ldl的編譯選項加上)。
一、引言
通常情況下,對函數庫的鏈接是放在編譯時期(compile time)完成的。所有相關的對象文件(object file)與牽涉到的函數庫(library)被鏈接合成一個可執行文件(executable file)。程序在運行時,與函數庫再無瓜葛,因為所有需要的函數已拷貝到自己門下。所以這些函數庫被成為靜態庫(static libaray),通常文件名為“libxxx.a”的形式。
其實,我們也可以把對一些庫函數的鏈接載入推遲到程序運行的時期(runtime)。這就是如雷貫耳的動態鏈接庫(dynamic link library)技術。
二、動態鏈接庫的特點與優勢
首先讓我們來看一下,把庫函數推遲到程序運行時期載入的好處:
1. 可以實現進程之間的資源共享。
什么概念呢?就是說,某個程序的在運行中要調用某個動態鏈接庫函數的時候,操作系統首先會查看所有正在運行的程序,看在內存里是否已有此庫函數的拷貝了。如果有,則讓其共享那一個拷貝;只有沒有才鏈接載入。這樣的模式雖然會帶來一些“動態鏈接”額外的開銷,卻大大的節省了系統的內存資源。C的標准庫就是動態鏈接庫,也就是說系統中所有運行的程序共享着同一個C標准庫的代碼段。
2. 將一些程序升級變得簡單。用戶只需要升級動態鏈接庫,而無需重新編譯鏈接其他原有的代碼就可以完成整個程序的升級。Windows 就是一個很好的例子。
3. 甚至可以真正坐到鏈接載入完全由程序員在程序代碼中控制。
程序員在編寫程序的時候,可以明確的指明什么時候或者什么情況下,鏈接載入哪個動態鏈接庫函數。你可以有一個相當大的軟件,但每次運行的時候,由於不同的操作需求,只有一小部分程序被載入內存。所有的函數本着“有需求才調入”的原則,於是大大節省了系統資源。比如現在的軟件通常都能打開若干種不同類型的文件,這些讀寫操作通常都用動態鏈接庫來實現。在一次運行當中,一般只有一種類型的文件將會被打開。所以直到程序知道文件的類型以后再載入相應的讀寫函數,而不是一開始就將所有的讀寫函數都載入,然后才發覺在整個程序中根本沒有用到它們。
三、動態鏈接庫的創建
由於動態鏈接庫函數的共享特性,它們不會被拷貝到可執行文件中。在編譯的時候,編譯器只會做一些函數名之類的檢查。在程序運行的時候,被調用的動態鏈接庫函數被安置在內存的某個地方,所有調用它的程序將指向這個代碼段。因此,這些代碼必須實用相對地址,而不是絕對地址。在編譯的時候,我們需要告訴編譯器,這些對象文件是用來做動態鏈接庫的,所以要用地址不無關代碼(Position Independent Code (PIC))。
對gcc編譯器,只需添加上 -fPIC 標簽,如:
gcc -fPIC -c file1.c
gcc -fPIC -c file2.c
gcc -shared libxxx.so file1.o file2.o
注意到最后一行,-shared 標簽告訴編譯器這是要建立動態鏈接庫。這與靜態鏈接庫的建立很不一樣,后者用的是 ar 命令。也注意到,動態鏈接庫的名字形式為 “libxxx.so” 后綴名為 “.so”
四、動態鏈接庫的使用
使用動態鏈接庫,首先需要在編譯期間讓編譯器檢查一些語法與定義。
這與靜態庫的實用基本一樣,用的是 -Lpath 和 -lxxx 標簽。如:
gcc file1.o file2.o -Lpath -lxxx -o program.exe
編譯器會先在path文件夾下搜索libxxx.so文件,如果沒有找到,繼續搜索libxxx.a(靜態庫)。
在程序運行期間,也需要告訴系統去哪里找你的動態鏈接庫文件。在UNIX下是通過定義名為 LD_LIBRARY_PATH 的環境變量來實現的。只需將path賦值給此變量即可。csh 命令為:
setenv LD_LIBRARY_PATH your/full/path/to/dll
一切安排妥當后,你可以用 ldd 命令檢查是否連接正常。
ldd program.exe
如果一切順利,它將會輸出你所用到的函數庫的清單。否則,將會抱怨無法找到某個庫。