Linux動(dòng)態(tài)鏈接庫的創(chuàng)建與使用
1. 介紹 ? ? 使用GNU的工具我們?nèi)绾卧贚inux下創(chuàng)建自己的程序函數(shù)庫?一個(gè)“程序函數(shù)庫”簡單的說就是一個(gè)文件包含了一些編譯好的代碼和數(shù)據(jù),這些編譯好的代碼和數(shù)據(jù)可以在事后供其他的程序使用。程序函數(shù)庫可以使整個(gè)程序更加模塊化,更容易重新編譯,而且更方便升級(jí)。 ?
? ? 程序函數(shù)庫可分為3種類型:靜態(tài)函數(shù)庫(static libraries)、共享函數(shù)庫(shared libraries)和動(dòng)態(tài)加載函數(shù)庫(dynamically loaded libraries)。 ? ? 靜態(tài)函數(shù)庫是在程序執(zhí)行前就加入到目標(biāo)程序中去了;而共享函數(shù)庫則是在程序啟動(dòng)的時(shí)候加載到程序中,它可以被不同的程序共享;動(dòng)態(tài)加載函數(shù)庫則可以在程序運(yùn)行的任何時(shí)候動(dòng)態(tài)的加載。實(shí)際上,動(dòng)態(tài)函數(shù)庫并非另外一種庫函數(shù)格式,區(qū)別是動(dòng)態(tài)加載函數(shù)庫是如何被程序員使用的。后面我們將舉例說明。
2. 靜態(tài)函數(shù)庫
? ? 靜態(tài)函數(shù)庫實(shí)際上就是簡單的一個(gè)普通的目標(biāo)文件的集合,一般來說習(xí)慣用“.a”作為文件的后綴。可以用ar這個(gè)程序來產(chǎn)生靜態(tài)函數(shù)庫文件。Ar是archiver的縮寫。靜態(tài)函數(shù)庫現(xiàn)在已經(jīng)不在像以前用得那么多了,主要是共享函數(shù)庫與之相比較有很多的優(yōu)勢的原因。慢慢地,大家都喜歡使用共享函數(shù)庫了。不過,在一些場所靜態(tài)函數(shù)庫仍然在使用,一來是保持一些與以前某些程序的兼容,二來它描述起來也比較簡單。?
? ? 靜態(tài)庫函數(shù)允許程序員把程序link起來而不用重新編譯代碼,節(jié)省了重新編譯代碼的時(shí)間。不過,在今天這么快速的計(jì)算機(jī)面前,一般的程序的重新編譯也花費(fèi)不了多少時(shí)間,所以這個(gè)優(yōu)勢已經(jīng)不是像它以前那么明顯了。靜態(tài)函數(shù)庫對(duì)開發(fā)者來說還是很有用的,例如你想把自己提供的函數(shù)給別人使用,但是又想對(duì)函數(shù)的源代碼進(jìn)行保密,你就可以給別人提供一個(gè)靜態(tài)函數(shù)庫文件。理論上說,使用ELF格式的靜態(tài)庫函數(shù)生成的代碼可以比使用共享函數(shù)庫(或者動(dòng)態(tài)函數(shù)庫)的程序運(yùn)行速度上快一些,大概1-5%。?
? ? 創(chuàng)建一個(gè)靜態(tài)函數(shù)庫文件,或者往一個(gè)已經(jīng)存在地靜態(tài)函數(shù)庫文件添加新的目標(biāo)代碼,可以用下面的命令:?
? ? ? ? ar rcs my_library.a file1.o file2.o?
? ? 這個(gè)例子中是把目標(biāo)代碼file1.o和file2.o加入到my_library.a這個(gè)函數(shù)庫文件中,如果my_library.a不存在則創(chuàng)建一個(gè)新的文件。在用ar命令創(chuàng)建靜態(tài)庫函數(shù)的時(shí)候,還有其他一些可以選擇的參數(shù),可以參加ar的使用幫助。這里不再贅述。
? ? 一旦你創(chuàng)建了一個(gè)靜態(tài)函數(shù)庫,你可以使用它了。你可以把它作為你編譯和連接過程中的一部分用來生成你的可執(zhí)行代碼。如果你用gcc來編譯產(chǎn)生可執(zhí)行代碼的話,你可以用“-l”參數(shù)來指定這個(gè)庫函數(shù)。你也可以用ld來做,使用它的“-l”和“-L”參數(shù)選項(xiàng)。具體用法可以參考info:gcc。?
3. 共享函數(shù)庫?
? ? 共享函數(shù)庫中的函數(shù)是在當(dāng)一個(gè)可執(zhí)行程序在啟動(dòng)的時(shí)候被加載。如果一個(gè)共享函數(shù)庫正常安裝,所有的程序在重新運(yùn)行的時(shí)候都可以自動(dòng)加載最新的函數(shù)庫中的函數(shù)。對(duì)于Linux系統(tǒng)還有更多可以實(shí)現(xiàn)的功能:?
升級(jí)了函數(shù)庫但是仍然允許程序使用老版本的函數(shù)庫。
當(dāng)執(zhí)行某個(gè)特定程序的時(shí)候可以覆蓋某個(gè)特定的庫或者庫中指定的函數(shù)。
可以在庫函數(shù)被使用的過程中修改這些函數(shù)庫。
3.1. 一些約定 ? ?如果你要編寫的共享函數(shù)庫支持所有有用的特性,你在編寫的過程中必須遵循一系列約定。你必須理解庫的不同的名字間的區(qū)別,例如它的“soname”和“real name”之間的區(qū)別和它們是如何相互作用的。你同樣還要知道你應(yīng)該把這些庫函數(shù)放在你文件系統(tǒng)的什么位置等等。下面我們具體看看這些問題。?
3.1.1. 共享庫的命名
? ? 每個(gè)共享函數(shù)庫都有個(gè)特殊的名字,稱作“soname”。soname名字命名必須以“l(fā)ib”作為前綴,然后是函數(shù)庫的名字,然后是“.so”,最后是版本號(hào)信息。不過有個(gè)特例,就是非常底層的C庫函數(shù)都不是以lib開頭這樣命名的。 ? ?每個(gè)共享函數(shù)庫都有一個(gè)真正的名字(“real name”),它是包含真正庫函數(shù)代碼的文件。真名有一個(gè)主版本號(hào),和一個(gè)發(fā)行版本號(hào)。最后一個(gè)發(fā)行版本號(hào)是可選的,可以沒有。主版本號(hào)和發(fā)行版本號(hào)使你可以知道你到底是安裝了什么版本的庫函數(shù)。另外,還有一個(gè)名字是編譯器編譯的時(shí)候需要的函數(shù)庫的名字,這個(gè)名字就是簡單的soname名字,而不包含任何版本號(hào)信息。
? ? 管理共享函數(shù)庫的關(guān)鍵是區(qū)分好這些名字。當(dāng)可執(zhí)行程序需要在自己的程序中列出這些他們需要的共享庫函數(shù)的時(shí)候,它只要用soname就可以了;反過來,當(dāng)你要?jiǎng)?chuàng)建一個(gè)新的共享函數(shù)庫的時(shí)候,你要指定一個(gè)特定的文件名,其中包含很細(xì)節(jié)的版本信息。當(dāng)你安裝一個(gè)新版本的函數(shù)庫的時(shí)候,你只要先將這些函數(shù)庫文件拷貝到一些特定的目錄中,運(yùn)行l(wèi)dconfig這個(gè)實(shí)用就可以。ldconfig檢查已經(jīng)存在的庫文件,然后創(chuàng)建soname的符號(hào)鏈接到真正的函數(shù)庫,同時(shí)設(shè)置/etc/ld.so.cache這個(gè)緩沖文件。這個(gè)我們稍后再討論。
? ? ldconfig并不設(shè)置鏈接的名字,通常的做法是在安裝過程中完成這個(gè)鏈接名字的建立,一般來說這個(gè)符號(hào)鏈接就簡單的指向最新的soname或者最新版本的函數(shù)庫文件。最好把這個(gè)符號(hào)鏈接指向soname,因?yàn)橥ǔ.?dāng)你升級(jí)你的庫函數(shù)后,你就可以自動(dòng)使用新版本的函數(shù)庫類。
? ? 我們來舉例看看:/usr/lib/libreadline.so.3 是一個(gè)完全的完整的soname,ldconfig可以設(shè)置一個(gè)符號(hào)鏈接到其他某個(gè)真正的函數(shù)庫文件,例如是/usr/lib/libreadline.so.3.0。同時(shí)還必須有一個(gè)鏈接名字,例如 /usr/lib/libreadline.so就是一個(gè)符號(hào)鏈接指向/usr/lib/libreadline.so.3。
3.1.2. 文件系統(tǒng)中函數(shù)庫文件的位置
? ? 共享函數(shù)庫文件必須放在一些特定的目錄里,這樣通過系統(tǒng)的環(huán)境變量設(shè)置,應(yīng)用程序才能正確的使用這些函數(shù)庫。大部分的源碼開發(fā)的程序都遵循GNU的一些標(biāo)準(zhǔn),我們可以看info幫助文件獲得相信的說明,info信息的位置是:info:standards#Directory_Variables。GNU標(biāo)準(zhǔn)建議所有的函數(shù)庫文件都放在/usr/local/lib目錄下,而且建議命令可執(zhí)行程序都放在/usr/local/bin目錄下。這都是一些習(xí)慣問題,可以改變的。?
? ? 文件系統(tǒng)層次化標(biāo)準(zhǔn)FHS(Filesystem Hierarchy Standard)(http://www.pathname.com/fhs)規(guī)定了在一個(gè)發(fā)行包中大部分的函數(shù)庫文件應(yīng)該安裝到/usr/lib目錄下,但是如果某些庫是在系統(tǒng)啟動(dòng)的時(shí)候要加載的,則放到/lib目錄下,而那些不是系統(tǒng)本身一部分的庫則放到/usr/local/lib下面。?
? ? 上面兩個(gè)路徑的不同并沒有本質(zhì)的沖突。GNU提出的標(biāo)準(zhǔn)主要對(duì)于開發(fā)者開發(fā)源碼的,而FHS的建議則是針對(duì)發(fā)行版本的路徑的。具體的位置信息可以看/etc/ld.so.conf里面的配置信息。
3.2. 這些函數(shù)庫如何使用
? ? 在基于GNU glibc的系統(tǒng)里,包括所有的linux系統(tǒng),啟動(dòng)一個(gè)ELF格式的二進(jìn)制可執(zhí)行文件會(huì)自動(dòng)啟動(dòng)和運(yùn)行一個(gè)program loader。對(duì)于Linux系統(tǒng),這個(gè)loader的名字是/lib/ld-linux.so.X(X是版本號(hào))。這個(gè)loader啟動(dòng)后,反過來就會(huì)load所有的其他本程序要使用的共享函數(shù)庫。
? ? 到底在哪些目錄里查找共享函數(shù)庫呢?這些定義缺省的是放在/etc/ld.so.conf文件里面,我們可以修改這個(gè)文件,加入我們自己的一些特殊的路徑要求。大多數(shù)RedHat系列的發(fā)行包的/etc/ld.so.conf文件里面不包括/usr/local/lib這個(gè)目錄,如果沒有這個(gè)目錄的話,我們可以修改/etc/ld.so.conf,自己手動(dòng)加上這個(gè)條目。
? ? 如果你想覆蓋某個(gè)庫中的一些函數(shù),用自己的函數(shù)替換它們,同時(shí)保留該庫中其他的函數(shù)的話,你可以在 /etc/ld.so.preload中加入你想要替換的庫(.o結(jié)尾的文件),這些preloading的庫函數(shù)將有優(yōu)先加載的權(quán)利。
? ? 當(dāng)程序啟動(dòng)的時(shí)候搜索所有的目錄顯然會(huì)效率很低,于是Linux系統(tǒng)實(shí)際上用的是一個(gè)高速緩沖的做法。ldconfig缺省情況下讀出/etc/ld.so.conf相關(guān)信息,然后設(shè)置適當(dāng)?shù)胤?hào)鏈接,然后寫一個(gè)cache到 /etc/ld.so.cache這個(gè)文件中,而這個(gè)/etc/ld.so.cache則可以被其他程序有效的使用了。這樣的做法可以大大提高訪問函數(shù)庫的速度。這就要求每次新增加一個(gè)動(dòng)態(tài)加載的函數(shù)庫的時(shí)候,就要運(yùn)行l(wèi)dconfig來更新這個(gè)cache,如果要?jiǎng)h除某個(gè)函數(shù)庫,或者某個(gè)函數(shù)庫的路徑修改了,都要重新運(yùn)行l(wèi)dconfig來更新這個(gè)cache。通常的一些包管理器在安裝一個(gè)新的函數(shù)庫的時(shí)候就要運(yùn)行l(wèi)dconfig。?
? ? 另外,F(xiàn)reeBSD使用cache的文件不一樣。FreeBSD的ELF cache是/var/run/ld-elf.so.hints,而a.out的cache則是/var/run/ld.so.hints。它們同樣是通過ldconfig來更新。
3.3. 環(huán)境變量
? ? 各種各樣的環(huán)境變量控制著一些關(guān)鍵的過程。例如你可以臨時(shí)為你特定的程序的一次執(zhí)行指定一個(gè)不同的函數(shù)庫。Linux系統(tǒng)中,通常變量LD_LIBRARY_PATH就是可以用來指定函數(shù)庫查找路徑的,而且這個(gè)路徑通常是在查找標(biāo)準(zhǔn)的路徑之前查找。這個(gè)是很有用的,特別是在調(diào)試一個(gè)新的函數(shù)庫的時(shí)候,或者在特殊的場合使用一個(gè)非標(biāo)準(zhǔn)的函數(shù)庫的時(shí)候。環(huán)境變量LD_PRELOAD列出了所有共享函數(shù)庫中需要優(yōu)先加載的庫文件,功能和/etc/ld.so.preload類似。這些都是有/lib/ld-linux.so這個(gè)loader來實(shí)現(xiàn)的。值得一提的是,LD_LIBRARY_PATH可以在大部分的UNIX-linke系統(tǒng)下正常起作用,但是并非所有的系統(tǒng)下都可以使用,例如HP-UX系統(tǒng)下,就是用SHLIB_PATH這個(gè)變量,而在AIX下則使用LIBPATH這個(gè)變量。
? ? LD_LIBRARY_PATH在開發(fā)和調(diào)試過程中經(jīng)常大量使用,但是不應(yīng)該被一個(gè)普通用戶在安裝過程中被安裝程序修改,大家可以去參考http://www.visi.com/~barr/ldpath.html,這里有一個(gè)文檔專門介紹為什么不使用LD_LIBRARY_PATH這個(gè)變量。
? ? 事實(shí)上還有更多的環(huán)境變量影響著程序的調(diào)入過程,它們的名字通常就是以LD_或者RTLD_打頭。大部分這些環(huán)境變量的使用的文檔都是不全,通常搞得人頭昏眼花的,如果要真正弄清楚它們的用法,最好去讀loader的源碼(也就是gcc的一部分)。
? ? 允許用戶控制動(dòng)態(tài)鏈接函數(shù)庫將涉及到setuid/setgid這個(gè)函數(shù),如果特殊的功能需要的話。因此,GNU loader通常限制或者忽略用戶對(duì)這些變量使用setuid和setgid。如果loader通過判斷程序的相關(guān)環(huán)境變量判斷程序的是否使用了setuid或者setgid,如果uid和euid不同,或者gid和egid部一樣,那么loader就假定程序已經(jīng)使用了setuid或者setgid,然后就大大的限制器控制這個(gè)老鏈接的權(quán)限。如果閱讀GNU glibc的庫函數(shù)源碼,就可以清楚地看到這一點(diǎn)。特別的我們可以看elf/rtld.c和sysdeps/generic/dl-sysdep.c這兩個(gè)文件。這就意味著如果你使得uid和gid與euid和egid分別相等,然后調(diào)用一個(gè)程序,那么這些變量就可以完全起效。
3.4. 創(chuàng)建一個(gè)共享函數(shù)庫
? ? 現(xiàn)在我們開始學(xué)習(xí)如何創(chuàng)建一個(gè)共享函數(shù)庫。其實(shí)創(chuàng)建一個(gè)共享函數(shù)庫非常容易。首先創(chuàng)建object文件,這個(gè)文件將加入通過gcc –fPIC參數(shù)命令加入到共享函數(shù)庫里面。PIC的意思是“位置無關(guān)代碼”(Position Independent Code)。下面是一個(gè)標(biāo)準(zhǔn)的格式:
? ? ? ? gcc -shared -Wl,-soname,your_soname -o library_name file_list library_list
? ? 下面再給一個(gè)例子,它創(chuàng)建兩個(gè)object文件(a.o和b.o),然后創(chuàng)建一個(gè)包含a.o和b.o的共享函數(shù)庫。例子中”-g”和“-Wall”參數(shù)不是必須的。
? ? ? ? gcc -fPIC -g -c -Wall a.c
? ? ? ? gcc -fPIC -g -c -Wall b.c
? ? ? ? gcc -shared -Wl,-soname,liblusterstuff.so.1 -o liblusterstuff.so.1.0.1 a.o b.o -lc
? ? 下面是一些需要注意的地方:
· ? ? ? ? ? ? ? ? ? 不用使用-fomit-frame-pointer這個(gè)編譯參數(shù)除非你不得不這樣。雖然使用了這個(gè)參數(shù)獲得的函數(shù)庫仍然可以使用,但是這使得調(diào)試程序幾乎沒有用,無法跟蹤調(diào)試。
· ? ? ? ? ? ? ? ? ? 使用-fPIC來產(chǎn)生代碼,而不是-fpic。
· ? ? ? ? ? ? ? ? ? 某些情況下,使用gcc 來生成object文件,需要使用“-Wl,-export-dynamic”這個(gè)選項(xiàng)參數(shù)。?
? ? 通常,動(dòng)態(tài)函數(shù)庫的符號(hào)表里面包含了這些動(dòng)態(tài)的對(duì)象的符號(hào)。這個(gè)選項(xiàng)在創(chuàng)建ELF格式的文件時(shí)候,會(huì)將所有的符號(hào)加入到動(dòng)態(tài)符號(hào)表中。可以參考ld的幫助獲得更詳細(xì)的說明。
3.5. 安裝和使用共享函數(shù)庫
? ? 一旦你定義了一個(gè)共享函數(shù)庫,你還需要安裝它。其實(shí)簡單的方法就是拷貝你的庫文件到指定的標(biāo)準(zhǔn)的目錄(例如/usr/lib),然后運(yùn)行l(wèi)dconfig。
? ? 如果你沒有權(quán)限去做這件事情,例如你不能修改/usr/lib目錄,那么你就只好通過修改你的環(huán)境變量來實(shí)現(xiàn)這些函數(shù)庫的使用了。首先,你需要?jiǎng)?chuàng)建這些共享函數(shù)庫;然后,設(shè)置一些必須得符號(hào)鏈接,特別是從soname到真正的函數(shù)庫文件的符號(hào)鏈接,簡單的方法就是運(yùn)行l(wèi)dconfig:
? ? ? ? ldconfig -n directory_with_shared_libraries ? ? 然后你就可以設(shè)置你的LD_LIBRARY_PATH這個(gè)環(huán)境變量,它是一個(gè)以逗號(hào)分隔的路徑的集合,這個(gè)可以用來指明共享函數(shù)庫的搜索路徑。例如,使用bash,就可以這樣來啟動(dòng)一個(gè)程序my_program:
? ? ? ? LD_LIBRARY_PATH=$LD_LIBRARY_PATH my_program
? ? 如果你需要的是重載部分函數(shù),則你就需要?jiǎng)?chuàng)建一個(gè)包含需要重載的函數(shù)的object文件,然后設(shè)置LD_PRELOAD環(huán)境變量。
? ? 通常你可以很方便的升級(jí)你的函數(shù)庫,如果某個(gè)API改變了,創(chuàng)建庫的程序會(huì)改變soname。然而,如果一個(gè)函數(shù)升級(jí)了某個(gè)函數(shù)庫而保持了原來的soname,你可以強(qiáng)行將老版本的函數(shù)庫拷貝到某個(gè)位置,然后重新命名這個(gè)文件(例如使用原來的名字,然后后面加.orig后綴),然后創(chuàng)建一個(gè)小的“wrapper”腳本來設(shè)置這個(gè)庫函數(shù)和相關(guān)的東西。例如下面的例子:
? ? ? ? #!/bin/sh export LD_LIBRARY_PATH=/usr/local/my_lib,$LD_LIBRARY_PATH
? ? ? ? exec /usr/bin/my_program.orig $*
? ? 我們可以通過運(yùn)行l(wèi)dd來看某個(gè)程序使用的共享函數(shù)庫。例如你可以看ls這個(gè)實(shí)用工具使用的函數(shù)庫:
? ? ? ? ldd /bin/ls
? ? ? ? libtermcap.so.2 => /lib/libtermcap.so.2 (0x4001c000)
? ? ? ? libc.so.6 => /lib/libc.so.6 (0x40020000)
? ? ? ? /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) ? ? ?通常我么可以看到一個(gè)soname的列表,包括路徑。在所有的情況下,你都至少可以看到兩個(gè)庫:
· ? ? ? ? ? ? ? ? ? /lib/ld-linux.so.N(N是1或者更大,一般至少2)。這是這個(gè)用于加載其他所有的共享庫的庫。
· ? ? ? ? ? ? ? ? ? ?libc.so.N(N應(yīng)該大于或者等于6)。這是C語言函數(shù)庫。
? ? 值得一提的是,不要在對(duì)你不信任的程序運(yùn)行l(wèi)dd命令。在ldd的manual里面寫得很清楚,ldd是通過設(shè)置某些特殊的環(huán)境變量(例如,對(duì)于ELF對(duì)象,設(shè)置LD_TRACE_LOADED_OBJECTS),然后運(yùn)行這個(gè)程序。這樣就有可能使得某地程序可能使得ldd來執(zhí)行某些意想不到的代碼,而產(chǎn)生不安全的隱患。
3.6. 不兼容的函數(shù)庫
? ? 如果一個(gè)新版的函數(shù)庫要和老版本的二進(jìn)制的庫不兼容,則soname需要改變。對(duì)于C語言,一共有4個(gè)基本的理由使得它們?cè)诙M(jìn)制代碼上很難兼容:
一個(gè)函數(shù)的行文改變了,這樣它就可能與最開始的定義不相符合。
· ? ? ? ? ?輸出的數(shù)據(jù)項(xiàng)改變了。
· ? ? ? ? ?某些輸出的函數(shù)刪除了。
· ? ? ? ? 某些輸出函數(shù)的接口改變了。 ? ?如果你能避免這些地方,你就可以保持你的函數(shù)庫在二進(jìn)制代碼上的兼容,或者說,你可以使得你的程序的應(yīng)用二進(jìn)制接口(ABI:Application Binary Interface)上兼容。
4. 動(dòng)態(tài)加載的函數(shù)庫Dynamically Loaded (DL) Libraries
? ? 動(dòng)態(tài)加載的函數(shù)庫Dynamically loaded (DL) libraries是一類函數(shù)庫,它可以在程序運(yùn)行過程中的任何時(shí)間加載。它們特別適合在函數(shù)中加載一些模塊和plugin擴(kuò)展模塊的場合,因?yàn)樗梢栽诋?dāng)程序需要某個(gè)plugin模塊時(shí)才動(dòng)態(tài)的加載。例如,Pluggable Authentication Modules(PAM)系統(tǒng)就是用動(dòng)態(tài)加載函數(shù)庫來使得管理員可以配置和重新配置身份驗(yàn)證信息。
? ? Linux系統(tǒng)下,DL函數(shù)庫與其他函數(shù)庫在格式上沒有特殊的區(qū)別,我們前面提到過,它們創(chuàng)建的時(shí)候是標(biāo)準(zhǔn)的object格式。主要的區(qū)別就是這些函數(shù)庫不是在程序鏈接的時(shí)候或者啟動(dòng)的時(shí)候加載,而是通過一個(gè)API來打開一個(gè)函數(shù)庫,尋找符號(hào)表,處理錯(cuò)誤和關(guān)閉函數(shù)庫。通常C語言環(huán)境下,需要包含這個(gè)頭文件。 ? ? Linux中使用的函數(shù)和Solaris中一樣,都是dlpoen() API。當(dāng)然不是所有的平臺(tái)都使用同樣的接口,例如HP-UX使用shl_load()機(jī)制,而Windows平臺(tái)用另外的其他的調(diào)用接口。如果你的目的是使得你的代碼有很強(qiáng)的移植性,你應(yīng)該使用一些wrapping函數(shù)庫,這樣的wrapping函數(shù)庫隱藏不同的平臺(tái)的接口區(qū)別。一種方法是使用glibc函數(shù)庫中的對(duì)動(dòng)態(tài)加載模塊的支持,它使用一些潛在的動(dòng)態(tài)加載函數(shù)庫界面使得它們可以夸平臺(tái)使用。具體可以參考http://developer.gnome.org/doc/API/glib/glib-dynamic-loading-of-modules.html. 另外一個(gè)方法是使用libltdl,是GNU libtool的一部分,可以進(jìn)一步參考CORBA相關(guān)資料。 ?
4.1. dlopen() ? ?dlopen函數(shù)打開一個(gè)函數(shù)庫然后為后面的使用做準(zhǔn)備。C語言原形是:
? ? ? ? void * dlopen(const char *filename, int flag);
? ? 如果文件名filename是以“/”開頭,也就是使用絕對(duì)路徑,那么dlopne就直接使用它,而不去查找某些環(huán)境變量或者系統(tǒng)設(shè)置的函數(shù)庫所在的目錄了。否則dlopen()就會(huì)按照下面的次序查找函數(shù)庫文件: ? ? ? ?1. 環(huán)境變量LD_LIBRARY指明的路徑。
? ? ? ? 2. /etc/ld.so.cache中的函數(shù)庫列表。
? ? ? ? 3. /lib目錄,然后/usr/lib。不過一些很老的a.out的loader則是采用相反的次序,也就是先查 /usr/lib,然后是/lib。 ? ?dlopen()函數(shù)中,參數(shù)flag的值必須是RTLD_LAZY或者RTLD_NOW,RTLD_LAZY的意思是resolve undefined symbols as code from the dynamic library is executed,而RTLD_NOW的含義是resolve all undefined symbols before dlopen() returns and fail if this cannot be done'。 ? ?如果有好幾個(gè)函數(shù)庫,它們之間有一些依賴關(guān)系的話,例如X依賴Y,那么你就要先加載那些被依賴的函數(shù)。例如先加載Y,然后加載X。
? ? dlopen()函數(shù)的返回值是一個(gè)句柄,然后后面的函數(shù)就通過使用這個(gè)句柄來做進(jìn)一步的操作。如果打開失敗dlopen()就返回一個(gè)NULL。如果一個(gè)函數(shù)庫被多次打開,它會(huì)返回同樣的句柄。 ? ? 如果一個(gè)函數(shù)庫里面有一個(gè)輸出的函數(shù)名字為_init,那么_init就會(huì)在dlopen()這個(gè)函數(shù)返回前被執(zhí)行。我們可以利用這個(gè)函數(shù)在我的函數(shù)庫里面做一些初始化的工作。我們后面會(huì)繼續(xù)討論這個(gè)問題的。 ?4.2. dlerror()
? ? 通過調(diào)用dlerror()函數(shù),我們可以獲得最后一次調(diào)用dlopen(),dlsym(),或者dlclose()的錯(cuò)誤信息。 4.3. dlsym()
? ? 如果你加載了一個(gè)DL函數(shù)庫而不去使用當(dāng)然是不可能的了,使用一個(gè)DL函數(shù)庫的最主要的一個(gè)函數(shù)就是dlsym(),這個(gè)函數(shù)在一個(gè)已經(jīng)打開的函數(shù)庫里面查找給定的符號(hào)。這個(gè)函數(shù)如下定義:
? ? ? ? void * dlsym(void *handle, char *symbol);
? ? 函數(shù)中的參數(shù)handle就是由dlopen打開后返回的句柄,symbol是一個(gè)以NIL結(jié)尾的字符串。如果dlsym()函數(shù)沒有找到需要查找的symbol,則返回NULL。如果你知道某個(gè)symbol的值不可能是NULL或者0,那么就很好,你就可以根據(jù)這個(gè)返回結(jié)果判斷查找的symbol是否存在了;不過,如果某個(gè)symbol的值就是NULL,那么這個(gè)判斷就有問題了。標(biāo)準(zhǔn)的判斷方法是先調(diào)用dlerror(),清除以前可能存在的錯(cuò)誤,然后調(diào)用dlsym()來訪問一個(gè)symbol,然后再調(diào)用dlerror()來判斷是否出現(xiàn)了錯(cuò)誤。一個(gè)典型的過程如下:
? ? ? ? dlerror(); /* clear error code */
? ? ? ? s = (actual_type) dlsym(handle, symbol_being_searched_for);
? ? ? ? if ((err = dlerror()) != NULL){
? ? ? ? /* handle error, the symbol wasn't found */
? ? ? ? }
? ? ? ? else
? ? ? ? {
? ? ? ? /* symbol found, its value is in s */
? ? ? ? }
4.4. dlclose()
? ? dlopen()函數(shù)的反過程就是dlclose()函數(shù),dlclose()函數(shù)用力關(guān)閉一個(gè)DL函數(shù)庫。Dl函數(shù)庫維持一個(gè)資源利用的計(jì)數(shù)器,當(dāng)調(diào)用dlclose的時(shí)候,就把這個(gè)計(jì)數(shù)器的計(jì)數(shù)減一,如果計(jì)數(shù)器為0,則真正的釋放掉。真正釋放的時(shí)候,如果函數(shù)庫里面有_fini()這個(gè)函數(shù),則自動(dòng)調(diào)用_fini()這個(gè)函數(shù),做一些必要的處理。Dlclose()返回0表示成功,其他非0值表示錯(cuò)誤。
4.5. DL Library Example
? ? 下面是一個(gè)例子。例子中調(diào)入math函數(shù)庫,然后打印2.0的余弦函數(shù)值。例子中每次都檢查是否出錯(cuò)。應(yīng)該是個(gè)不錯(cuò)的范例:
1. ? ? ? int main(int argc, char **argv)
2. ? ? ? {
3. ? ? ? ? void *handle;
4. ? ? ? ? double (*cosine)(double);
5. ? ? ? ? char *error;
6. ? ? ? ? handle = dlopen ("/lib/libm.so.6", RTLD_LAZY);
7. ? ? ? ? if (!handle)
8. ? ? ? ? {
9. ? ? ? ? ? fputs (dlerror(), stderr);
10. ? ? ? exit(1);
11. ? ? }
12. ? ?
13. ? ? cosine = dlsym(handle, "cos");
14. ? ? if ((error = dlerror()) != NULL)
15. ? ? {
16. ? ? ? fputs(error, stderr);
17. ? ? ? exit(1);
18. ? ? }
19. ? ? printf ("%f ", (*cosine)(2.0));
20. ? ? dlclose(handle);
21. ? }
? ? 如果這個(gè)程序名字叫foo.c,那么用下面的命令來編譯:
? ? ? ? gcc -o foo foo.c –ldl
?
評(píng)論
查看更多