Linux: .o,.a,.so Windows:obj,lib,dll各種庫解析
windows下obj,lib,dll,exe的關係
lib是和dll對應的。lib是靜態連結庫的庫檔案,dll是動態連結庫的庫檔案。
所謂靜態就是link的時候把裡面需要的東西抽取出來安排到你的exe檔案中,以後執行你的exe的時候不再需要lib。
所謂動態就是exe執行的時候依賴於dll裡面提供的功能,沒有這個dll,你的exe無法執行。
lib,dll,exe都算是最終的目標檔案,是最終產物。而c/c++屬於原始碼。原始碼和最終目標檔案中過渡的就是中間程式碼obj,實際上之所以需要中間程式碼,是你不可能一次得到目標檔案。比如說一個exe需要很多的cpp檔案生成。而編譯器一次只能編譯一個cpp檔案。這樣編譯器編譯好一個cpp以後會將其編譯成obj,當所有必須要的cpp都編譯成obj以後,再統一link成所需要的exe,應該說缺少任意一個obj都會導致exe的連結失敗。
1.obj裡存的是編譯後的程式碼跟資料,並且有名稱,所以在連線時有時會出現未解決的外部符號的問題。當連成exe後便不存在名稱的概念了,只有地址。lib就是一堆obj的組合。
2.理論上可以連線obj檔案來引用其他工程(可以認為一個obj檔案等價於編譯生成它的cpp檔案,可以引用obj來替換cpp,也可以新增cpp來替換obj ),但實際中通常用lib來實現工程間相互引用。
3.編譯器會預設連結一些常用的庫,其它的需要你自己指定。
lib和DLL的區別
(1)lib是編譯時需要的,dll是執行時需要的。如果要完成原始碼的編譯,有lib就夠了。如果也使動態連線的程式執行起來,有dll就夠了。在開發和除錯階段,當然最好都有。
(2) 一般的動態庫程式有lib檔案和dll檔案。lib檔案是必須在編譯期就連線到應用程式中的,而dll檔案是執行期才會被呼叫的。如果有dll檔案,那麼對應的lib檔案一般是一些索引資訊,具體的實現在dll檔案中。如果只有lib檔案,那麼這個lib檔案是靜態編譯出來的,索引和實現都在其中。 靜態編譯的lib檔案有好處:給使用者安裝時就不需要再掛動態庫了。但也有缺點,就是導致應用程式比較大,而且失去了動態庫的靈活性,在版本升級時,同時要釋出新的應用程式才行。
(3)在動態庫的情況下,有兩個檔案,一個是引入庫(.LIB)檔案(實際上也算是一個靜態庫,只是在連結時只能把函式在DLL的入口連結到exe中,而不像真正靜態連結庫那樣將函式體真正連結到exe中 ,通過lib進行的動態連結實際上也使用了靜態連結來實現 ),一個是DLL檔案,引入庫檔案包含被DLL匯出的函式的名稱和位置,DLL包含實際的函式和資料,應用程式使用LIB檔案連結到所需要使用的DLL檔案,庫中的函式和資料並不複製到可執行檔案中,因此在應用程式的可執行檔案中,存放的不是被呼叫的函式程式碼,而是DLL中所要呼叫的函式的記憶體地址,這樣當一個或多個應用程式執行是再把程式程式碼和被呼叫的函式程式碼連結起來,從而節省了記憶體資源。從上面的說明可以看出,DLL和.LIB檔案必須隨應用程式一起發行,否則應用程式將會產生錯誤。
DLL內的函式分為兩種:
(1)DLL匯出函式,可供應用程式呼叫;
(2)DLL內部函式,只能在DLL程式使用,應用程式無法呼叫它們
建立靜態連結庫和建立動態連結庫
VC6中建立[Win32 Dynamic-Link Library]工程便可以創建出一個空的DLL工程.
VC6中建立[Win32 Static Library]工程便可以創建出一個空的LIB工程(靜態連結庫工程,僅生成一個lib檔案).
新增lib檔案的常用辦法有二個:
1、把*.lib放在VC的Lib目錄中
2、修改project setting的Link->Input中的Addtional library path,加入你的目錄dll:是可實際執行的二進位制程式碼,有定位程式碼的!
3、也可以在object/library中直接寫上lib檔案路徑.(這裡實際上是可以寫上任意obj檔案或者lib檔案的).
linux .o,.a,.so
.o,是目標檔案,相當於windows中的.obj檔案
.so 為共享庫,是shared object,用於動態連線的,相當於windows下的dll
.a為靜態庫,是好多個.o合在一起,用於靜態連線
靜態函式庫
特點:實際上是簡單的普通目標檔案的集合,在程式執行前就加入到目標程式中。
優點:可以用以前某些程式相容;描述簡單;允許程式設計師把程式link起來而不用重新編譯程式碼,節省了重新編譯程式碼的時間(該優勢目前已不明顯);開發者可以對原始碼保密;理論上使用ELF格式的靜態庫函式生成的程式碼可以比使用共享或動態函式庫的程式執行速度快(大概1%-5%)
生成:使用ar程式(archiver的縮寫)。ar rcs my_lib.a f1.o f2.o是把目的碼f1.o和f2.o加入到my_lib.a這個函式庫檔案中(如果my_lib.a不存在則建立)
使用:用gcc生成可執行程式碼時,使用-l引數指定要加入的庫函式。也可以用ld命令的-l和-L引數。
共享函式庫
共享函式庫在可執行程式啟動的時候載入,所有程式重新執行時都可自動載入共享函式庫中的函式。.so檔案感覺很複雜,光是命名規則就已經看得我很暈了~整理一下,共享庫需要:soname、real name,另外編譯的時候名字也有說法。依次解釋下:
soname:必須的格式:lib+函式庫名+.so+版本號資訊(但是記住,非常底層的C庫函式都不是以lib開頭命名的)。例子:/usr/lib/libreadline.so.3
real name:顧名思義是真正的名字啦,有主版本號和發行版本號。但是沒找到例項……
編譯器編譯的時候需要的函式庫的名字就是不包含版本號資訊的soname,例如上面的例子把最後的.3去掉就可以了。
位置:共享函式庫檔案必須放在特定目錄,對於開放原始碼來說,GNU標準建議所有的函式庫檔案都放在/usr/local/lib目錄下,而且建議命令、可執行程式都放在/usr/local/bin目錄下。不過這個只是習慣啦,可以改變,具體的位置資訊可以看/etc/ld.so.conf裡面的配置資訊。當然,也可以修改這個檔案,加入自己的一些特殊的路徑要求。
建立:在網上找到了gcc方式和easyeclipse環境下兩種建立方式。
gcc方式:
首先建立object檔案,這個檔案將加入通過gcc –fPIC 引數命令加入到共享函式庫裡面,標準格式:gcc -shared -Wl,-soname,your_soname -o library_name file_list library_list(說實話這個標準格式看起來好複雜,我找了個例項,但是好像和那個標準格式稍有不同:gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so)
在easyeclipse環境下生成.so檔案:
1.選擇新建工程,建立一個c++工程
2.在工程型別選項裡選擇 Shared Library,然後填入工程名字PXXX點選完成即可。
3.編寫程式,然後編譯就會在debug或者release裡生成一個libPXXX.so檔案,如果不要lib的起頭標記點選project選單的Properties選項,然後在彈出的介面的右邊點選Build artifact頁面,將Output prefix選項的內容清空即可。
4.如果是C++程式,注意在介面函式的前面加上extern "C"標記,在標頭檔案加上如下標記:
#ifdef __cplusplus
#extern "C"{
#endif
標頭檔案主體
#ifdef __cplusplus
}
#endif
如果不加以上標記,經過編譯後,so裡的函式名並非你編寫程式時設定的函式名,在開發環境左側的工程檔案列表中點開debug項裡的PXXX.o可以看到so檔案裡的函式名都是在你設定的函式名後面加了一個__Fi標記,比如你用的設定的函式名稱是Func(), 而so裡的函式名則為Func__Fi()或者其他的名稱。
安裝:拷貝共享庫檔案到指定的標準的目錄,然後執行ldconfig。如果沒有許可權這樣做,那麼就只好通過修改環境變數來實現這些函式庫的使用了。方法不再說了,很複雜。
檢視:可以通過執行ldd來看某個程式使用的共享函式庫。例如ldd /bin/ls。檢視.so檔案使用nm命令,如nm libXXX.so。(注意,nm對於靜態的函式庫和共享的函式庫都起作用)
關於覆蓋:如果想用自己的函式覆蓋某個庫中的一些函式,同時保留該庫中其他的函式的話,可以在/etc/ld.so.preload中加入要替換的庫(.o結尾的檔案),這些preloading的庫函式將有優先載入的權利。
關於更新:每次新增加動態載入的函式庫、刪除某個函式庫或者修改某個函式庫的路徑時,都要重新執行ldconfig來更新快取檔案/etc/ld.so.cache,此檔案儲存已排好序的動態連結庫名字列表
(在Linux下,共享庫的載入是由/lib/ld.so完成的,ld.so載入共享庫時,會從ld.so.cache查詢)
我們通常把一些公用函式製作成函式庫,供其它程式使用。函式庫分為靜態庫和動態庫兩
種。靜態庫在程式編譯時會被連線到目的碼中,程式執行時將不再需要該靜態庫。動態
庫在程式編譯時並不會被連線到目的碼中,而是在程式執行是才被載入,因此在程式運
行時還需要動態庫存在。本文主要通過舉例來說明在Linux中如何建立靜態庫和動態庫,以
及使用它們。
在建立函式庫前,我們先來準備舉例用的源程式,並將函式庫的源程式編譯成.o檔案。
第1步:編輯得到舉例的程式--hello.h、hello.c和main.c;
hello.c(見程式2)是函式庫的源程式,其中包含公用函式hello,該函式將在螢幕上輸出"
Hello XXX!"。hello.h(見程式1)為該函式庫的標頭檔案。main.c(見程式3)為測試庫檔案的
主程式,在主程式中呼叫了公用函式hello。
程式1: hello.h
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif //HELLO_H
程式2: hello.c
#include <stdio.h>
void hello(const char *name)
{
printf("Hello %s!\n", name);
}
程式3: main.c
#include "hello.h"
int main()
{
hello("everyone");
return 0;
}
第2步:將hello.c編譯成.o檔案;
無論靜態庫,還是動態庫,都是由.o檔案建立的。因此,我們必須將源程式hello.c通過g
cc先編譯成.o檔案。
在系統提示符下鍵入以下命令得到hello.o檔案。
# gcc -c hello.c
#
我們執行ls命令看看是否生存了hello.o檔案。
# ls
hello.c hello.h hello.o main.c
#
在ls命令結果中,我們看到了hello.o檔案,本步操作完成。
下面我們先來看看如何建立靜態庫,以及使用它。
第3步:由.o檔案建立靜態庫;
靜態庫檔名的命名規範是以lib為字首,緊接著跟靜態庫名,副檔名為.a。例如:我們將
建立的靜態庫名為myhello,則靜態庫檔名就是libmyhello.a。在建立和使用靜態庫時,
需要注意這點。建立靜態庫用ar命令。
在系統提示符下鍵入以下命令將建立靜態庫檔案libmyhello.a。
# ar -cr libmyhello.a hello.o
#
我們同樣執行ls命令檢視結果:
# ls
hello.c hello.h hello.o libmyhello.a main.c
#
ls命令結果中有libmyhello.a。
第4步:在程式中使用靜態庫;
靜態庫製作完了,如何使用它內部的函式呢?只需要在使用到這些公用函式的源程式中包
含這些公用函式的原型宣告,然後在用gcc命令生成目標檔案時指明靜態庫名,gcc將會從
靜態庫中將公用函式連線到目標檔案中。注意,gcc會在靜態庫名前加上字首lib,然後追
加副檔名.a得到的靜態庫檔名來查詢靜態庫檔案。
在程式3:main.c中,我們包含了靜態庫的標頭檔案hello.h,然後在主程式main中直接呼叫公
用函式hello。下面先生成目標程式hello,然後執行hello程式看看結果如何。
法一 # gcc -o hello main.c -L. –lmyhello,或gcc main.c -L. –lmyhello -o hello自定義的庫時,main.c還可放在-L.和 –lmyhello之間,但是不能放在它倆之後,否則會提示myhello沒定義,但是是系統的庫時,如g++ -o main(-L/usr/lib) -lpthread main.cpp就不出錯。
法二 #gcc main.c libmyhello.a -o hello或gcc -o hello main.c libmyhello.a
法三:先生成main.o:gcc -c main.c ,再生成可執行檔案:gcc -o hello main.o libmyhello.a或gccmain.o libmyhello.a -o hello ,動態庫連線時也可以這樣做。
# ./hello
Hello everyone!
#
我們刪除靜態庫檔案試試公用函式hello是否真的連線到目標檔案 hello中了。
# rm libmyhello.a
rm: remove regular file `libmyhello.a'? y
# ./hello
Hello everyone!
#
程式照常執行,靜態庫中的公用函式已經連線到目標檔案中了。
我們繼續看看如何在Linux中建立動態庫。我們還是從.o檔案開始。
第5步:由.o檔案建立動態庫檔案;
動態庫檔名命名規範和靜態庫檔名命名規範類似,也是在動態庫名增加字首lib,但其
副檔名為.so。例如:我們將建立的動態庫名為myhello,則動態庫檔名就是libmyh
ello.so。用gcc來建立動態庫。
在系統提示符下鍵入以下命令得到動態庫檔案libmyhello.so。
# gcc -shared -fPIC -o libmyhello.so hello.o (-o不可少)
#
我們照樣使用ls命令看看動態庫檔案是否生成。
# ls
hello.c hello.h hello.o libmyhello.so main.c
#
第6步:在程式中使用動態庫;
在程式中使用動態庫和使用靜態庫完全一樣,也是在使用到這些公用函式的源程式中包含
這些公用函式的原型宣告,然後在用gcc命令生成目標檔案時指明動態庫名進行編譯。我們
先執行gcc命令生成目標檔案,再執行它看看結果。
# gcc -o hello main.c -L. -lmyhello
(或 #gcc main.c libmyhello.so -o hello 不會出錯(沒有libmyhello.so的話,會出錯),但是接下來./hello 會提示出錯,因為雖然連線時用的是當前目錄的動態庫,但是執行時,是到/usr/lib中找庫檔案的,將檔案libmyhello.so複製到目錄/usr/lib中就OK了)
# ./hello
./hello: error while loading shared libraries: libmyhello.so: cannot open shar
ed object file: No such file or directory
#
哦!出錯了。快看看錯誤提示,原來是找不到動態庫檔案libmyhello.so。程式在執行時,
會在/usr/lib和/lib等目錄中查詢需要的動態庫檔案。若找到,則載入動態庫,否則將提
示類似上述錯誤而終止程式執行。我們將檔案libmyhello.so複製到目錄/usr/lib中,再試
試。
# mv libmyhello.so /usr/lib
# ./hello
Hello everyone!
#
成功了。這也進一步說明了動態庫在程式執行時是需要的。
我們回過頭看看,發現使用靜態庫和使用動態庫編譯成目標程式使用的gcc命令完全一樣,
那當靜態庫和動態庫同名時,gcc命令會使用哪個庫檔案呢?抱著對問題必究到底的心情,
來試試看。
先刪除除.c和.h外的所有檔案,恢復成我們剛剛編輯完舉例程式狀態。
# rm -f hello hello.o /usr/lib/libmyhello.so
# ls
hello.c hello.h main.c
#
在來建立靜態庫檔案libmyhello.a和動態庫檔案libmyhello.so。
在生成動態庫時,需要使用-fPIC,這樣才能生成位置無關的程式碼,達到程式碼段和資料段共享的目的
# gcc -c -fpic hello.c //編譯hello.c時也需要加上-fpic選項,否則rodata' can not be used when making a shared object; recompile with -fPIC
# ar -cr libmyhello.a hello.o (或-cvr )
# gcc -shared -fPIC -o libmyhello.so hello.o
# ls
hello.c hello.h hello.o libmyhello.a libmyhello.so main.c
#
通過上述最後一條ls命令,可以發現靜態庫檔案libmyhello.a和動態庫檔案libmyhello.s
o都已經生成,並都在當前目錄中。然後,我們執行gcc命令來使用函式庫myhello生成目標
檔案hello,並執行程式 hello。
# gcc -o hello main.c -L. –lmyhello (動態庫和靜態庫同時存在時,優先使用動態庫, 當然,直接#gcc main.c libmyhello.a -o hello的話,就是指定為靜態庫了)
# ./hello
./hello: error while loading shared libraries: libmyhello.so: cannot open shar
ed object file: No such file or directory
#
從程式hello執行的結果中很容易知道,當靜態庫和動態庫同名時,gcc命令將優先使用動態庫,預設去連/usr/lib和/lib等目錄中的動態庫,將檔案libmyhello.so複製到目錄/usr/lib中即可。
Note:
編譯引數解析
最主要的是GCC命令列的一個選項:
-shared 該選項指定生成動態連線庫(讓聯結器生成T型別的匯出符號表,有時候也生成弱連線W型別的匯出符號),不用該標誌外部程式無法連線。相當於一個可執行檔案
-fPIC 作用於編譯階段,告訴編譯器產生與位置無關程式碼(Position-Independent Code)。那麼在產生的程式碼中,沒有絕對地址,全部使用相對地址,故而程式碼可以被載入器載入到記憶體的任意位置,都可以正確的執行。這正是共享庫所要求的,共享庫被載入時,在記憶體的位置不是固定的。
如果不加fPIC,則編譯出來的程式碼在載入時需要根據載入到的位置進行重定位(因為它裡面的程式碼並不是位置無關程式碼),如果被多個應用程式共同使用,那麼它們必須每個程式維護一份so的程式碼副本了.(因為so被每個程式載入的位置都不同,顯然這些重定位後的程式碼也不同,當然不能共享)。
不用此選項的話編譯後的程式碼是位置相關的,所以動態載入時是通過程式碼拷貝的方式來滿足不同程序的需要,而不能達到真正程式碼段共享的目的。
-L. 表示要連線的庫在當前目錄中;(多個庫:在編譯命令列中,將使用的靜態庫檔案放在原始檔後面就可以了。比如:gcc -L/usr/lib myprop.c libtest.a libX11.a libpthread.a -o myprop
其中-L/usr/lib指定庫檔案的查詢路徑。編譯器預設在當前目錄下先查詢指定的庫檔案,如前面的“法二 #gccmain.c libmyhello.a-o hello”)
-lmyhello 編譯器查詢動態連線庫時有隱含的命名規則,即在給出的名字前面加上lib,後面加上.so或.a來確定庫的名稱libmyhello.so或libmyhello.a。
LD_LIBRARY_PATH這個環境變數指示動態聯結器可以裝載動態庫的路徑。
當然如果有root許可權的話,可以修改/etc/ld.so.conf檔案,然後呼叫 /sbin/ldconfig來達到同樣的目的,不過如果沒有root許可權,那麼只能採用輸出LD_LIBRARY_PATH的方法了。
呼叫動態庫的時候有幾個問題會經常碰到,有時,明明已經將庫的標頭檔案所在目錄 通過 “-I” include進來了,庫所在檔案通過 “-L”引數引導,並指定了“-l”的庫名,但通過ldd命令察看時,就是死活找不到你指定連結的so檔案,這時你要作的就是通過修改 LD_LIBRARY_PATH或者/etc/ld.so.conf檔案來指定動態庫的目錄。通常這樣做就可以解決庫無法連結的問題了。
靜態庫連結時搜尋路徑順序:
1. ld(GNU linker)會去找GCC命令中的引數-L
編譯過程是分為四個階段:預處理(也稱預編譯,Preprocessing)、編譯(Compilation)、彙編 (Assembly)和連線(link) 【連結】
2. 再找gcc的環境變數LIBRARY_PATH
3. 再找內定目錄 /lib /usr/lib /usr/local/lib 這是當初compile gcc時寫在程式內的
動態連結時、執行時搜尋路徑順序:
1. 編譯目的碼時指定的動態庫搜尋路徑
2. 環境變數LD_LIBRARY_PATH指定的動態庫搜尋路徑
3. 配置檔案/etc/ld.so.conf中指定的動態庫搜尋路徑
4. 預設的動態庫搜尋路徑/lib
5. 預設的動態庫搜尋路徑/usr/lib
有關環境變數:
LIBRARY_PATH環境變數:指定程式靜態連結庫檔案搜尋路徑
LD_LIBRARY_PATH環境變數:指定程式動態連結庫檔案搜尋路徑
另:
從上述可知,如何找到生成的動態庫有3種方式:
(1)把庫拷貝到/usr/lib和/lib目錄下。
(2)在LD_LIBRARY_PATH環境變數中加上庫所在路徑。
例如動態庫libhello.so在/home/example/lib目錄下:
export LD_LIBRARY_PATH=LD_LIBRARY_PATH:/home/example/lib
(3) 修改/etc/ld.so.conf檔案,把庫所在的路徑加到檔案末尾(直接寫在檔案末尾,不要在路徑前加include),並執行ldconfig重新整理(ldconfig 命令的用途,主要是在預設搜尋目錄(/lib和/usr/lib)以及動態庫配置檔案/etc/ld.so.conf內所列的目錄下,搜尋出可共享的動態連結庫(格式如前介紹,lib*.so*),進而創建出動態裝入程式(ld.so)所需的連線和快取檔案.快取檔案預設為/etc/ld.so.cache,此檔案儲存已排好序的動態連結庫名字列表.)。這樣,加入的目錄下的所有庫檔案都可見。
附:像下面這樣指定路徑去連線系統的靜態庫,會報錯說要連線的庫找不到:
g++ -o main main.cpp -L/usr/lib libpthread.a
必須這樣g++ -o main main.cpp -L/usr/lib -lpthread才正確 。
自定義的庫考到/usr/lib 下時,
g++ -o main main.cpp -L/usr/lib libpthread.a libthread.a libclass.a會出錯,但是這樣g++ -o main main.cpp -L/usr/lib -lpthread -lthread -lclass就正確了。