Linux下靜態、動態庫(隱式、顯式調用)的創建和使用及區別

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






發佈了28 篇原創文章 · 獲贊 40 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章