Linux下靜態、動態庫(隱式、顯式調用)的創建和使用及區別
一、靜態鏈接庫的創建與使用:
1、編寫add.c 及main.c代碼:
/**************************************************************************//*add.c*/
int add(int x, int y)
{
return x + y;
return 0;
}
/*************************************************************************/
然後add.h代碼爲:
/*add.h*/
#ifndef _ADD_H_
#define _ADD_H_
int add(int, int);
#endif
/***************************************************************************/
main函數代碼:
/*main.c*/
#include <stdio.h>
int main(void)
{
printf("2+3= %d\n", add(2,3));
return 0;
}
/**********************************************************************************/
2、現在首先要明確我們目的是將add.c做成靜態鏈接庫,然後main.c調用生成的靜態鏈接庫中的add()
(1)將add.c做成靜態鏈接庫(創建靜態庫):首先將add.c編譯成目標文件(add.o文件),如下:
#gcc -c add.c //生成add.o
然後將生成的目標文件(add.o)生成靜態庫libadd.a:
#ar crv libadd.a add.o //生成libadd.a
(2)靜態庫做好了,就可以在編譯main.c時將靜態庫鏈接進去了,接下來就編譯生成可執行文件(靜態庫的使用):
#gcc -o exe main.c -I. -L. -ladd
//或者 #gcc -o exe main.c ./libadd.a
//再或者#gcc -o exe main.c -L. libadd.a
(注:這裏的-I/路徑, -L/路徑, 是通過-I和-L指定對應的庫文件名和庫文件的路徑,這裏就是當前目錄,
libadd.a就是要用的靜態庫,這樣對應的靜態庫已經編譯到對應的可執行程序中。執行對應的可執行文件
便可以得到對應函數調用的結果。在main.c中不需要包含導出文件的頭文件。
上面的(2)分開就是:
1)編譯生成對應的目標文件:
#gcc -c -I/home/hcj/xxxxxxxx main.c
2)生成可執行文件:
#gcc -o exe -L/home/hcj/xxxxxxxx main.o libstr.a
還有若主函數是C++程序(即.cpp),則需要在main.cpp中用extern "C"{}包含被調用函數(add.c)的
頭文件,編譯時用g++編譯或者還用gcc編譯但需加上一個鏈接c++庫的參數(-lstdc++)
)
(3)最後執行可執行程序:
#./exe
二、動態鏈接庫的創建與使用:
1、把add.c編譯生成動態庫(創建動態庫):
#gcc -fPIC -c add.c //生成add.o#gcc -shared -o libadd.so add.o /* 或者 #ar crv libadd.so add.o */
(上面兩行可以整合成一行:#gcc -fPIC -shared -o libadd.so add.c)
注:-fpic 使輸出的對象模塊是按照可重定位地址方式生成的(即與位置無關)。
-shared指定把對應的源文件生成對應的動態鏈接庫文件libstr.so文件
2、動態庫的使用(動態鏈接庫分:隱式調用和顯式調用2種):
(1)隱式調用:
動態鏈接庫(隱式調用)在代碼上與寫靜態鏈接庫沒什麼區別,主要是在編譯時。代碼編寫與靜態庫一樣,不需要包含導出函數的頭文件,若主函數是C++程序(即.cpp),則需要在main.cpp中用
extern "C"{}包含被調用函數(add.c)的頭文件(這裏需要包含頭文件是與.cpp和.c混合編譯有關,同靜態\動態庫
無關),編譯時用g++編譯或者還用gcc編譯但需加上一個鏈接c++庫的參數(-lstdc++)
1)代碼編寫: 與靜態庫一樣
2)編譯main.c生成可執行程序(動態庫隱式調用的使用):
#gcc -o exe main.c ./libadd.so
(或者 #gcc -o exe main.c -L. libadd.so
再或者將libadd.so copy到目錄 /usr/lib或/lib中,然後執行:
#gcc -o exe main.c libadd.so //此時不需要指定搜索路徑
)
注: 最直接最簡單的方法就是把libadd.so拉到/usr/lib或/lib中去。 、
還有一種方法 export LD_LIBRARY_PATH=$(pwd)
另外還可以在/etc/ld.so.conf文件里加入我們生成的庫的目錄,然後執行#/sbin/ldconfig。
/etc/ld.so.conf是非常重要的一個目錄,裏面存放的是鏈接器和加載器搜索共享庫時要檢查的目錄,
默認是從/usr/lib /lib中讀取的,所以想要順利運行,我們也可以把我們庫的目錄加入到這個文件中
並執行/sbin/ldconfig 。另外還有個文件需要了解/etc/ld.so.cache,裏面保存了常用的動態函數
庫,且會先把他們加載到內存中,因爲內存的訪問速度遠遠大於硬盤的訪問速度,這樣可以提高軟件加載
動態函數庫的速度了。
3)#./exe
(2)顯式調用:
顯式調用的動態庫的創建與隱式調用相同。(隱式調用與靜態庫的使用方法一樣,不需要包含導出函數的頭文件(顯式調用也不用包含頭文件),只需要在編譯可執行程序時指定庫文件的路徑)
顯式調用和隱式調用的區別在於:編譯可執行程序時需要指定庫文件的搜索路徑,而顯式調用編譯可執行程序時不用加上
動態庫的搜索路徑(因爲已經在主函數中包含了庫文件的路徑),但是需要增加幾個系統調用:
(#include <dlfcn.h>, //頭文件
1)dlopen()
第一個參數:指定共享庫的名稱,將會在下面位置查找指定的共享庫。
-環境變量LD_LIBRARY_PATH列出的用分號間隔的所有目錄。
-文件/etc/ld.so.cache中找到的庫的列表,用ldconfig維護。
-目錄usr/lib。
-目錄/lib。
-當前目錄。
第二個參數:指定如何打開共享庫。
-RTLD_NOW:將共享庫中的所有函數加載到內存
-RTLD_LAZY:會推後共享庫中的函數的加載操作,直到調用dlsym()時方加載某函數
返回值:返回動態庫的句柄
2)dlsym()
調用dlsym時,利用dlopen()返回的共享庫的句柄以及函數名稱作爲參數,返回要加載函數的入口地址。
3)dlclose()
關閉動態鏈接庫
4)dlerror()
該函數用於檢查調用共享庫的相關函數出現的錯誤。
如果dlerror返回值不爲空,則dlsym執行出錯
)
1、編寫add.c 及main.c代碼:
/**************************************************************************/
/*add.c*/
int add(int x, int y)
{
return x + y;
return 0;
}
/*************************************************************************/
然後add.h代碼爲:
/*add.h*/
#ifndef _ADD_H_
#define _ADD_H_
int add(int, int);
#endif
/***************************************************************************/
main函數代碼:
/*main.c*/
#include <stdio.h>
#include <dlfcn.h> //顯式加載需要用到的頭文件
#define LIB "./libadd.so" //指定動態庫路徑
int main(void)
{
void *dl;
char *error;
int (*func)();
dl = dlopen(LIB, RTLD_LAZY); /*打開動態鏈接庫*/
if(dl == NULL)
{
printf("Failed load libary\n");
}
error = dlerror(); /*檢測錯誤*/
if(error != NULL)
{
printf("%s\n", error);
return -1;
}
func = dlsym(dl, "test"); /*獲取函數的地址*/
error = dlerror(); /*檢測錯誤*/
if(error != NULL)
{
printf("%s\n", error);
return -1;
}
func(); /*調用動態庫中函數*/
dlclose(dl); /*關閉共享庫*/
error = dlerror(); /*檢測錯誤*/
if(error != NULL)
{
printf("%s\n", error);
return -1;
}
return 0;
}
/**********************************************************************************/
2、編譯main.c生成可執行程序,動態庫的創建已經在上面講了(動態庫顯式調用):
1)#gcc -ldl -o exe mian.c
注意要添加-ldl選項,以使用顯式調用相關的函數調用。
可以看到,顯式調用的代碼看上去要複雜很多,但是卻比隱式調用要靈活,我們不必在編譯時就確定要加載哪個
動態鏈接庫,可以在運行時再確定,甚至重新加載。
2)#./exe