linux動(dòng)/靜態(tài)庫(kù)的生成和使用
在小烏的眼里,庫(kù)文件就是資源文件,也沒(méi)有什么難以理解的;可是如果真要問(wèn)得深入一點(diǎn):“動(dòng)態(tài)鏈接庫(kù)和靜態(tài)鏈接庫(kù)有什么區(qū)別?”,“怎么做一個(gè)動(dòng)態(tài)鏈接庫(kù)?”,“怎么生成靜態(tài)/動(dòng)態(tài)鏈接庫(kù)?”,“什么叫顯示/隱示調(diào)用?”。。。小烏就郁悶了,所以今天決定要拍死這些問(wèn)題。
Window下面的動(dòng)/靜態(tài)鏈接庫(kù)文件名分別為:.dll和.lib;
Linux下則為:.so或.so.x和.a;.so文件的標(biāo)準(zhǔn)形式應(yīng)該為:libxxx.so或libxxx.so.y,前綴的lib是為了系統(tǒng)能識(shí)別它,后綴的.y則是版本號(hào),可有可無(wú);靜態(tài)鏈接庫(kù)對(duì)于linux來(lái)說(shuō)叫共享庫(kù)或許來(lái)的比較標(biāo)準(zhǔn),靜態(tài)庫(kù)文件.a則可以看做是目標(biāo)文件.o的一個(gè)集合,形式也必須為libxxx.a。
.lib對(duì).a,.dll對(duì).so?;蛟S從名字及應(yīng)用的OS來(lái)看,沒(méi)什么太大的聯(lián)系,但實(shí)際上都是換湯不換藥,一個(gè)故事,所以在這里拿到一起來(lái)說(shuō)。依小烏之見(jiàn),所有的庫(kù)文件實(shí)際上都是資源文件,也就是說(shuō),作為“備選的資源”而存在,只將必要的部分導(dǎo)入到自己的程序中;但是區(qū)別是:靜態(tài)庫(kù)必要的目標(biāo)代碼的是在對(duì)程序編譯的時(shí)候被加入到程序中,而動(dòng)態(tài)庫(kù)的目標(biāo)代碼是在被調(diào)用的時(shí)候加入到程序中,所以相比之下動(dòng)態(tài)庫(kù)更加靈活,也沒(méi)有象靜態(tài)庫(kù)那樣存在對(duì)系統(tǒng)資源浪費(fèi)的問(wèn)題。但是靜態(tài)庫(kù)是不是就沒(méi)有他存在的意義呢?不見(jiàn)得,有人舉了這么一個(gè)例子:如果你用libpcap庫(kù)編了一個(gè)程序,要給被人運(yùn)行,而他的系統(tǒng)上沒(méi)有裝pcap庫(kù),該怎么解決呢?最簡(jiǎn)單的辦法就是編譯該程序時(shí)把所有要鏈接的庫(kù)都鏈接它們的靜態(tài)庫(kù),這樣,就可以在別人的系統(tǒng)上直接運(yùn)行該程序了。雖然現(xiàn)在小烏還沒(méi)有完全理解。。。但是,存在即合理。
在Windows系統(tǒng)下的執(zhí)行文件格式是PE格式,動(dòng)態(tài)庫(kù)需要一個(gè)DllMain函數(shù)作為初始化的人口,通常在導(dǎo)出函數(shù)的聲明時(shí)需要有_declspec(dllexport)關(guān)鍵字。Linux下的gcc編譯的執(zhí)行文件默認(rèn)是ELF格式,不需要初始化入口,亦不需要到函數(shù)做特別聲明,編寫(xiě)比較方便。
說(shuō)說(shuō)linux下生成動(dòng)/靜態(tài)鏈接庫(kù),下面是小烏的一個(gè)Makefile文件:
#makefile for libmy.so
libwuxian.so : getdate.o gettime.o
gcc -shared -o libwuxian.so getdate.o gettime.o
rm -f *.o
getdate.o : datetime.h
gcc -c getdate.c
gettime.o : datetime.h
gcc -c gettime.c
別被弄蒙了,這是小烏拿來(lái)練習(xí)的Makefile文件。最重要的一句話gcc -shared -o libwuxian.so getdate.o gettime.o,在shell下其實(shí)也就一句話:gcc -shared -o libwuxian.so getdate.c gettime.c,-shared說(shuō)明生成的是動(dòng)態(tài)鏈接庫(kù),-o后面接生成的動(dòng)態(tài)鏈接庫(kù)文件名,必須以lib打頭,.so或者.so.x結(jié)尾,x為版本號(hào)。關(guān)于Makefile文件的寫(xiě)法,請(qǐng)看:Makefile的傻瓜寫(xiě)法。
對(duì)于靜態(tài)庫(kù),則應(yīng)該如下:
gcc -c xx.c
gcc -c ll.c
ar cqs libwx.a xx.o ll.o 或者ar r libwx.a xx.o ll.o
xx.c為源文件,xx.o為目標(biāo)文件,ar cqs libwx.so xx.o ll.o是將目標(biāo)文件xx.o及ll.o鏈接為libwx.so,靜態(tài)庫(kù)名必須以lib打頭,.a結(jié)尾。
再說(shuō)說(shuō)動(dòng)/靜態(tài)鏈接庫(kù)的使用。靜態(tài)庫(kù)是在編譯時(shí)簡(jiǎn)單的將被用到的目標(biāo)代碼加入到可執(zhí)行程序,所以我們先說(shuō)它的使用:
# gcc -c main.c -o main.o
# gcc main.o -o qqq -L. -lwx
使用gcc編譯,假設(shè)我們這里所有的文件都保存在同一個(gè)目錄下,第一句是將main.c編譯成目標(biāo)文件,第二句則是將目標(biāo)文件libwx.a和main.o鏈接到可執(zhí)行程序中。
動(dòng)態(tài)庫(kù)的使用則分為顯式調(diào)用和隱式調(diào)用,顯式調(diào)用需要了解以下幾個(gè)函數(shù):
const char *dlerror(void);
當(dāng)動(dòng)態(tài)鏈接庫(kù)操作函數(shù)執(zhí)行失敗時(shí),dlerror可以返回出錯(cuò)信息,為NULL時(shí)表示操作函數(shù)執(zhí)行成功。
void *dlopen (const char *filename, int flag);
成功則返回為void*的句柄。flag可以為RTLD_LAZY(表明在動(dòng)態(tài)鏈接庫(kù)的函數(shù)代碼執(zhí)行時(shí)解決);RTLD_NOW(表明在dlopen返回前就解決所有未定義的符號(hào),一旦未解決,dlopen將返回錯(cuò)誤)。filename為動(dòng)態(tài)庫(kù)路徑。
void *dlsym(void *handle, char *symbol);
dlsym根據(jù)動(dòng)態(tài)鏈接庫(kù)操作句柄(handle)與符號(hào)(實(shí)際上就是欲調(diào)用的函數(shù)名),返回符號(hào)對(duì)應(yīng)的函數(shù)的執(zhí)行代碼地址。由此地址,可以帶參數(shù)執(zhí)行相應(yīng)的函數(shù)。
int dlclose (void *handle);
參數(shù)為動(dòng)態(tài)鏈接庫(kù)的句柄。
顯式調(diào)用動(dòng)態(tài)鏈接庫(kù)必須包含動(dòng)態(tài)鏈接庫(kù)功能接口dlfcn.h(包含dl系列函數(shù)的聲明)和被調(diào)函數(shù)的聲明。看起來(lái)還比較簡(jiǎn)單,但是當(dāng)你對(duì)動(dòng)態(tài)鏈接庫(kù)函數(shù)使用比較頻繁的時(shí)候,你就知道他的麻煩了,所以,不推崇。
隱式調(diào)用。所謂隱式調(diào)用,就是調(diào)用的時(shí)候直接使用動(dòng)態(tài)庫(kù)中的函數(shù),并不區(qū)別對(duì)待。但是在隱式調(diào)用的時(shí)候必須要讓程序能找到你所調(diào)用的函數(shù)的所屬動(dòng)態(tài)庫(kù)。我們先來(lái)建立一個(gè)感性認(rèn)識(shí):
# more /etc/ld.so.conf 你會(huì)看到以下內(nèi)容:
/usr/kerberos/lib
/usr/X11R6/lib
/usr/lib/sane
/usr/lib/qt-3.1/lib
/usr/lib/mysql
/usr/lib/qt2/lib
/usr/local/lib
/usr/local/BerkeleyDB.4.3/lib
ld.so.conf是系統(tǒng)對(duì)動(dòng)態(tài)鏈接庫(kù)進(jìn)行查找的路徑配置文件,也就是說(shuō)該文件是系統(tǒng)鏈接工具/usr/bin/ld查找動(dòng)態(tài)鏈接庫(kù)的地圖,所以,要達(dá)到我們的目的有以下幾種方法:
1. 將自己的動(dòng)態(tài)鏈接庫(kù)文件拷到以上路徑的目錄下。
# cp libwx.so.1 /usr/local/lib
2. 將自己動(dòng)態(tài)鏈接庫(kù)文件的路徑加入到該文件中。
要么# vi /etc/ld.so.conf, 然后加入自己的路徑
要么# pwd >>/etc/ld.so.conf
注意:千萬(wàn)不要將>>寫(xiě)成>,前者是添加,后者是覆蓋,不相信你自己玩玩看。
3. 把當(dāng)前路徑加入環(huán)境變量LD_LIBRARY_PATH,其實(shí)就是/usr/bin/ld的環(huán)境變量。
# export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
編譯的時(shí)候:
# gcc -o qqq main.c libmy.so.1
如果沒(méi)有讓/usr/bin/ld知道你的動(dòng)態(tài)鏈接庫(kù)在哪,編譯的時(shí)候就要告訴它:
# gcc -o qqq main.c /root/wx/libmy.so.1
或者:# gcc -L/root/wx -o qqq main.c libmy.so.1
-L指定動(dòng)態(tài)鏈接庫(kù)所在的目錄,有時(shí)候用gcc還會(huì)碰到-I,-l,他們分別指定頭文件的目錄和所鏈接的動(dòng)態(tài)鏈接庫(kù)。
另外說(shuō)一個(gè)shell命令:
# ldd qqq
用來(lái)查看可執(zhí)行文件qqq的動(dòng)態(tài)鏈接庫(kù)的依賴關(guān)系。
創(chuàng)建共享庫(kù)的過(guò)程如下所述:
1。編譯目標(biāo)文件時(shí)使用GCC的 -fPIC選項(xiàng),這能產(chǎn)生于位置無(wú)關(guān)的代碼并能加載到任何地址。
2。使用GCC的 -shared 和 -soname選項(xiàng)。
3。使用GCC的-Wl選項(xiàng)把參數(shù)傳遞給鏈接器ld。
4。使用GCC的-l選項(xiàng)顯示地鏈接C庫(kù),以保證可以得到所需的啟動(dòng)代碼(startup)代碼,從而避免程序在使用不同的,可能是
不兼容版本的C庫(kù)的系統(tǒng)上不能啟動(dòng)執(zhí)行。
<------Makefile----->
CC=gcc
CFLAGS = -g -O2 -Wall
LIB_NAME=libtest.so
LIB_VER=
OBJS=test1.o test2.o
all:$(OBJS)
$(CC) $(CFLAGS) -shared -Wl,-soname,$(LIB_NAME) -o $(LIB_NAME).$(LIB_VER) $(OBJS) -lc
%.o: %.c
%(CC) $(CFLAGS) -fPIC -c $< -o $@
<------EOF--------->
聯(lián)系客服