轉自:Linux下動態鏈接庫的創建和使用
http://blog.csdn.net/xlxxcc/article/details/51074150
1、鏈接庫的基本知識
庫是一種軟件組件技術,庫裏面封裝了數據和函數。它的使用,可以是程序模塊化。在程序中使用,我們可以稱之爲程序函數庫。
程序函數庫可分爲3種類型:靜態函數庫(static libraries)、共享函數庫(shared libraries)、動態函數庫(dynamically loaded libraries):
1、靜態函數庫,是在程序執行前就加入到目標程序中去了;
2、共享函數庫,則是在程序啓動的時候加載到程序中,它可以被不同的程序共享
3、動態函數庫,並非另外一種庫函數格式,它只是使用動態加載方式加載共享函數庫。
Windows系統包括靜態鏈接庫(.lib文件)和動態鏈接庫(.dll文件)。
Linux通常把庫文件存放在/usr/lib或/lib目錄下
Linux庫文件名由:前綴lib、庫名和後綴3部分組成,其中共享鏈接庫以.so.X最爲後綴, .X是版本號,靜態鏈接庫通常以.a作爲後綴。
Linux下標準庫鏈接的三種方式(全靜態 , 半靜態 (libgcc,libstdc++), 全動態。 三種標準庫鏈接方式的選項及區別見下表。
三種標準庫鏈接方式的選項及區別:
標準庫鏈接方式 | 示例連接選項 | 優點 | 缺點 |
---|---|---|---|
全靜態 | -static -pthread -lrt -ldl | 不會發生不同Linux 版本下的標準庫不兼容問題 | 生成的文件比較大,應用程序功能受限(不能調用動態庫等) |
全動態 | -pthread -lrt -ldl | 生成的文件最小 | 不同Linux版本下標準庫依賴不兼容問題 |
半靜態(libgcc,libstdc++) | -static-libgcc -L. -pthread -lrt -ldl | 靈活度大,針對不同的標準庫採取不同的鏈接策略,從而避免不兼容問題發生 | 難識別哪些庫容易發生不兼容問題,會因選擇的標準庫版本而喪失某些功能 |
上述三種標準庫鏈接方式中,比較特殊的是 半靜態鏈接方式,主要在於其還需要在鏈接前增加額外的一個步驟:
ln -s ‘g++ -print-file-name=libstdc++.a’,作用是將 libstdc++.a(libstdc++ 的靜態庫)符號鏈接到本地工程鏈接目錄
-print-file-name在gcc中的解釋如下: Display the full path to library
ldd 簡介:該命令用於打印出某個應用程序或者動態庫所依賴的動態庫 ,使用該命令我們可以觀察到Linux標準庫三種鏈接方式的區別。
從實際應用當中發現,最理想的標準庫鏈接方式就是半靜態鏈接,通常會選擇將 libgcc 與 libstdc++ 這兩個標準庫靜態鏈接,從而避免應用程序在不同 Linux 版本間標準庫依賴不兼容的問題發生。
size 簡介:該命令用於顯示出可執行文件的大小
示例鏈接選項中所涉及命令(引用 GCC 原文):
- -llibrary
- -l library:指定所需要的額外庫
- -Ldir:指定庫搜索路徑
- -static:靜態鏈接所有庫
- -static-libgcc:靜態鏈接 gcc 庫
- -static-libstdc++:靜態鏈接 c++ 庫
2、靜態鏈接庫的創建和使用
涉及命令:ar, ar是創建、修改、提取靜態庫的操作。
ar -t 顯示靜態庫的內容
ar -d 從庫中刪除成員文件
ar -r 在庫中加入成員文件,若存在,則替換
ar -c 創建一個庫
ar -s 無論ar命令是否修改了庫內容,都強制重新生成庫符號表
步驟如下:
1、在一個頭文件種聲明靜態庫所導出的函數。
2、在一個源文件種實現靜態庫所導出的函數。
3、編譯源文件,生成可執行代碼。
4、將可執行代碼所在的目標文件加入到某個靜態庫中,並將靜態庫拷貝到系統默認的存放庫文件的目錄下。
下面通過一個例子來說明:mylib.h種存放的是靜態庫提供給用戶使用的函數的聲明,mylib.c實現了mylib.h種聲明的函數。
mylib.h
#ifndef _MYLIB_H_
#define _MYLIB_H_
void weclome(void);
void outString(const char *str);
#endif
mylib.cpp
#include "mylib.h"
void welcome(void){
printf("welcome to libmylib\n");
}
void outString(const char *str){
if(str != NULL)
printf("%s\n", str);
}
test.cpp
#include "mylib.h"
#include
int main(void){
printf("create and use library:\n");
welcome();
outString("it's successful\n");
return 0;
}
- 編譯mylib.c生成目標文件:gcc -o mylib.o -c mylib.cpp
- 將目標文件加入到靜態庫中:ar rcs libmylib.a mylib.o
- 將靜態庫copy到Linux的庫目錄(/usr/lib或者/lib)下:cp libmylib.a /usr/lib/libmylib.a
- 使用靜態庫編譯,編譯時無需帶上前綴和後綴:gcc -o test test.cpp -lmylib
- 運行可執行程序test: ./test
合併靜態鏈接庫的腳本代碼清單:
echo CREATE demo.a > ar.mac
echo SAVE >> ar.mac
echo END >> ar.mac
ar -M < ar.mac
ar -q demo.a CdtLog.o
echo OPEN demo.a > ar.mac
echo ADDLIB xml.a >> ar.mac
echo SAVE >> ar.mac
echo END >> ar.mac
ar -M < ar.mac
rm ar.mac
Linux makefile 中使用 ar 腳本方式進行靜態庫的創建,可以編寫如下代碼:
define BUILD_LIBRARY
$(if $(wildcard $@),@$(RM) $@)
$(if $(wildcard ar.mac),@$(RM) ar.mac)
$(if $(filter %.a, $^),
@echo CREATE $@ > ar.mac
@echo SAVE >> ar.mac
@echo END >> ar.mac
@$(AR) -M < ar.mac
)
$(if $(filter %.o,$^),@$(AR) -q $@ $(filter %.o, $^))
$(if $(filter %.a, $^),
@echo OPEN $@ > ar.mac
$(foreach LIB, $(filter %.a, $^),
@echo ADDLIB $(LIB) >> ar.mac
)
@echo SAVE >> ar.mac
@echo END >> ar.mac
@$(AR) -M < ar.mac
@$(RM) ar.mac
)
endef
$(TargetDir)/$(TargetFileName):$(OBJS)
$(BUILD_LIBRARY)
- 1
Linux 靜態庫鏈接順序問題及解決方法:
爲了解決這種庫鏈接順序問題,我們需要增加一些鏈接選項 :
(CXX)(LINKFLAGS) (OBJS)−Xlinker"−("(LIBS) -Xlinker “-)” -o $@
通過將所有需要被鏈接的靜態庫放入 -Xlinker “-(” 與 -Xlinker “-)” 之間,可以是 g++ 鏈接過程中, 自動循環鏈接所有靜態庫,從而解決了原本的鏈接順序問題。
3、共享函數庫的創建和使用
GNU標準建議所有的函數庫文件都放在/usr/local/lib目錄下,而且建議命令可執行程序都放在/usr/local/bin目錄下。
文件系統層次化標準FHS(Filesystem Hierarchy Standard)規定了在一個發行包中大部分的函數庫文件應該安裝到/usr/lib目錄下,但是如果某些
庫是在系統啓動的時候要加載的,則放到/lib目錄下,而那些不是系統本身一部分的庫則放到/usr/local/lib下面。
上面兩個路徑的不同並沒有本質的衝突。GNU提出的標準主要對於開發者開發源碼的,而FHS的建議則是針對發行版本的路徑的。具體的置信息可以看
/etc/ld.so.conf裏面的配置信息,通過對它的修改,可以增加自己的目錄。
如果你想覆蓋某個庫中的一些函數,用自己的函數替換它們,同時保留該庫中其他的函數的話,你可以在 /etc/ld.so.preload中加入你想要替換的庫
(.o結尾的文件),這些preloading的庫函數將有優先加載的權ldconfig可以更新/etc/ld.so.cache。/etc/ld.so.cache可以大大提高訪問函數庫的速度。
HP-UX系統下,就是用SHLIB_PATH這個變量,而在AIX下則使用LIBPATH這個變量。
共享函數庫創建的一個標準命令格式:
gcc -shared -Wl,-soname,your_soname -o library_name file_list library_list
例子:
- 1、創建Object文件:
gcc -fPIC -g -c -Wall a.c
gcc -fPIC -g -c -Wall b.c - 2、創建共享函數庫
gcc -shared -Wl,-soname,liblusterstuff.so.1 -o liblusterstuff.so.1.0.1 a.o b.o -lc
如果是C++項目,最簡單是使用Cmake來完成共享庫的創建,步驟如下:
如果創建的是JNI鏈接庫,則需要將 jdk/include/jni.h 和 jdk/include/linux/jni_md.h 複製到 /usr/include 目錄下。反正執行make命令的時候將會報錯
1、確保gcc-c++編譯環境, 安裝命令::
yum install gcc-c++2、安裝Cmake
wget https://cmake.org/files/v3.5/cmake-3.5.1.tar.gz
tar -xvf cmake-3.5.1.tar.gz
cd cmake-3.5.1
./bootstrap
make
make install3、如果您使用的windows系統,則將您的項目上傳到Linux,進入Linux下該項目的文件夾,創建CMakeLists.txt,內容格式如下:
CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
# cpp 文件
SET(test_SRCS
source/test1.cpp
source/test2.cpp
......
)
# 頭文件
SET(test_HDRS
include/test1.h
include/test2.h
.....
)
INCLUDE_DIRECTORIES(include)
# test: 是生產的庫的名字, 這裏可以加上SHARED或者STATIC或者MODULE,分別表示動態庫、靜態庫、模塊。不加則默認是靜態庫
ADD_LIBRARY(test SHARED/STATIC/MODULE ${test_SRCS} ${test_HDRS})
# 生成可執行文件
# ADD_EXECUTABLE(test ${test_SRCS} ${test_HDRS})
- 4、創建動態鏈接庫:
ccmake directory #用於配置編譯選項,如VTK_DIR目錄,一般這一步不需要配置
cmake directory #用於根據CMakeLists.txt生成Makefile文件
make #用於執行Makefile文件,編譯程序,生成可執行文件
共享函數庫的使用
一旦你定義了一個共享函數庫,你還需要安裝它。其實簡單的方法就是拷貝你的庫文件到指定的標準的目錄(例如/usr/lib),然後運行ldconfig。
如果你沒有權限去做這件事情, 那麼最簡單的方法就是運行ldconfig:
ldconfig -n directory_with_shared_libraries
然後設置LD_LIBRARY_PATH這個環境變量,它是一個以逗號分隔的路徑的集合:
LD_LIBRARY_PATH=$LD_LIBRARY_PATH,my_program
如果一個新版的函數庫要和老版本的二進制的庫不兼容,則soname需要改變。對於C語言,有四種情況會出現不兼容問題:
· 一個函數的行文改變了,這樣它就可能與最開始的定義不相符合。
· 輸出的數據項改變了。
· 某些輸出的函數刪除了。
· 某些輸出函數的接口改變了。**
4、共享函數庫的動態加載
共享函數庫可以在程序運行過程中的任何時間加載,它們特別適合在函數中加載一些模塊和plugin擴展模塊的場合,因爲它可以在當程序需要某個
plugin模塊時才動態的加載。
Linux系統下,DL函數庫與其他函數庫在格式上沒有特殊的區別。通常C語言環境下,需要包含這個頭文件。 Linux中使用的函數和Solaris中一樣,都是
dlpoen()API。當然不是所有的平臺都使用同樣的接口,例如HP-UX使用shl_load()機制,而Windows平臺用另外的其他的調用接口。
dlopen()
dlopen函數打開一個函數庫然後爲後面的使用做準備。C語言原形是:
void * dlopen(const char *filename, int flag);
如果文件名filename是以“/”開頭,也就是使用絕對路徑,那麼dlopne就直接使用它,而不去查找某些環境變量或者系統設置的函數庫所在的目錄了。
否則dlopen()就會按照下面的次序查找函數庫文件:
1. 環境變量LD_LIBRARY指明的路徑。
2. /etc/ld.so.cache中的函數庫列表。
3. /lib目錄,然後/usr/lib。不過一些很老的a.out的loader則是採用相反的次序,也就是先查 /usr/lib,然後是/lib。
dlopen()函數中,參數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'
注意函數庫的加載順序。
dlerror()
通過調用dlerror()函數,我們可以獲得最後一次調用dlopen(),dlsym(),或者dlclose()的錯誤信息。
dlsym()
void * dlsym(void *handle, char *symbol);
函數中的參數handle就是由dlopen打開後返回的句柄,symbol是一個以NIL結尾的字符串。
如果dlsym()函數沒有找到需要查找的symbol,則返回NULL。典型的調用過程如下:
dlerror(); /*clear error code */
s = (actual_type)dlsym(handle, symbol_being_searched_for);
if((error = dlerror()) != NULL){
/* handle error, the symbol wasn't found */
} else {
/* symbol found, its value is in s */
}
dlclose()
dlopen()函數的反過程就是dlclose()數,dlclose()函數用力關閉一個DL函數庫。真正釋放的時候,如果函數庫裏面有_fini()這個函數,則自動調用_fini()這個函數,做一些必要的處理。Dlclose()返回0表示成功,其他非0值表示錯誤。
動態函數庫的創建:
動態函數庫並非另外一種庫函數格式,可只是在程序運行的任何時候動態的加載的共享函數庫或。它的創建可以參考共享函數庫的創建。
動態函數庫的使用:
int main(int argc, char *argv){
void *handle;
char *error;
double (*cosine )(double);
handle = dlopen("/lib/libm.so.6", RTLD_LAZY);
if(!handle){
fputs(dlerror(), stderr);
exit(1);
}
cosine = dlsym(handle, "cos");
if((error = dlerror()) != NULL){
fputs(error, stderr);
exit(1);
}
printf("%f", (*cosine)(2, 0));
dlclose(handle);
return 0;
}
如果這個程序名字叫test.c,那麼用下面的命令來編譯:
gcc -o test test.c –ldl