共享庫和靜態庫

http://blog.chinaunix.net/uid-26833883-id-3219335.html


一、什麼是庫


本質上來說庫是一種可執行代碼的二進制形式,可以被操作系統載入內存執行。由於windows和linux的本質不同,因此二者庫的二進制是不兼容的。

Linux操作系統支持的函數庫分爲靜態庫和動態庫,動態庫又稱共享庫。Linux系統有幾個重要的目錄存放相應的函數庫,如/lib    /usr/lib。

二、靜態函數庫、動態函數庫

A.  這類庫的名字一般是libxxx.a;利用靜態函數庫編譯成的文件比較大,因爲整個函數庫的所有數據都被整合進目標代碼中,他的優點就顯而易見了,即編譯後的執行程序不需要外部的函數庫支持,因爲所有使用的函數都已經被編譯進可執行文件了。當然這也會稱爲它的缺點,因爲如果靜態函數庫改變了,那麼你的程序必須重新編譯,而且體積也較大。

B.這類庫德名字一般是libxxx.so,動態庫又稱共享庫;相對於靜態函數庫,動態函數庫在編譯的時候並沒有被編譯進目標代碼中,你的程序執行到相關函數時才調用函數庫裏的相應函數,因此動態函數庫所產生的可執行文件比較小。由於函數庫沒有被整合進你的程序,而是程序運行時動態申請並調用,所以程序的運行環境中必須提供相應的庫。動態函數庫的改變並不影響你的程序,所以動態函數庫的升級比較方便。而且如果多個應用程序都要使用同一函數庫,動態庫就非常適合,可以減少應用程序的體積。

注意:不管是靜態函數庫還是動態函數庫,都是由*.o目標文件生成。

三、函數庫的創建

A.靜態函數庫的創建

ar -cr  libname.a   test1.o  test2.o

ar:靜態函數庫創建的命令
-c :create的意思
-r :replace的意思,表示當前插入的模塊名已經在庫中存在,則替換同名的模塊。如果若干模塊中有一個模塊在庫中不存在,ar顯示一個錯誤信息,並不替換其他同名的模塊。默認的情況下,新的成員增加在庫德結尾處。

B.動態函數庫的創建

gcc -shared  -fpic  -o libname.so  test1.c test2.c

-fpic:產生代碼位置無關代碼



-shared :生成共享庫

四、靜態庫和動態庫的使用 

案例:

add.c
#include 

int add(int a,int b)
{
return a + b;
}
sub.c
#include 

int sub(int a,int b)
{
return a - b;
}

head.h

#ifndef _HEAD_H_
#define _HEAD_H_
extern int add(int a,int b);
extern int sub(int a,int b);
#endif
main.c
#include 

int main(int argc,char *argv[])
{
int a,b;

if(argc < 3)
{
fprintf(stderr,"Usage : %s argv[1] argv[2].\n",argv[0]);
return -1;
}
a = atoi(argv[1]);
b = atoi(argv[2]);

printf("a + b = %d\n",add(a,b));
printf("a - b = %d\n",sub(a,b));

return 0;
}
生成靜態庫



生成動態庫:



使用生成的生成的庫:



其中
-L 指定函數庫查找的位置,注意L後面還有'.',表示在當前目錄下查找
-l則指定函數庫名,其中的lib和.a(.so)省略。

注意:-L是指定查找位置,-l指定需要操作的庫名。

從上面的運行結果中,我們可以看到:

A.當動態庫和靜態庫同時存在的時候,gcc默認使用的是動態庫。如果強制使用靜態庫則需要加-static選項支持。
B.動態庫生成的可執行文件,test1不能正常的運行。
C.鏈接靜態庫的可執行程序明顯比鏈接動態庫的可執行文件大。

五、讓鏈接動態庫的可執行程序正常運行。

當系統加載可執行代碼時候,能夠知道其所依賴的庫的名字,但是還需要知道絕對路勁。此時就需要系統動態載入器(dynamic  linker/loader)。

對於elf格式的可執行程序,是由ld-linux.so*來完成的,它先後搜索elf文件的DT_RPATH段---環境變量LD_LIBRARY_PATH、/etc/ld.so.cache文件列表、/usr/lib、/lib目錄找到庫文件後將其載入內存。

A.一種最直接的方法,就是把生成的動態庫拷貝到/usr/lib或/lib中去。



B.使用LD_LIBRARY_PATH環境變量,這個環境變量在ubuntu操作系統中默認沒有,需要手動添加



C.動態在安裝在其他目錄下,如果想操作系統能找到它,可以通過一下步驟

<1>新建並編輯/etc/ld.so.conf.d/my.conf文件,加入庫所在目錄的路徑
<2>執行ldconfig命令更新ld.so.cache文件



此時,在執行鏈接動態庫的可執行文件則可以正常運行。

六、查看庫中的符號

A.nm命令可以打印出庫中涉及到的所有符號。庫既可以是靜態庫也可以是動態的。

常見的三種符號:
<1>在庫中被調用,但沒有在庫中定義(表明需要其他庫支持),用U表示
<2>在庫中定義的函數,用T表示
<3>“弱態”符號,他們雖然在庫中被定義,但是可能被其他庫中同名的符號覆蓋,用W表示。



B.ldd命令可以查看一個可執行程序依賴的共享庫



七、動態加載庫

用gcc -shared生成的我們稱爲動態庫(共享庫),其中動態庫在運行的過程中有兩種方式

A.動態鏈接

這種方式下,可執行程序只是做一個動態的鏈接,當需要用到動態庫中的函數時,有加載器隱士的加載。

B.動態加載

這種方式下,在可執行程序的內部,我們可以用dlopen()這樣的函數,手動進行加載,dlsym()函數找到我們想要調用函數的入口地址,然後進行調用。這種方式,在寫插件程序中得到廣泛應用。

相關的API:




<1>dlopen()打開一個新的動態庫,並把它裝入內存。該函數主要用來記載庫中的符號,這些符號在編譯的時候是不知道的。

dlopen()函數需要兩個參數:一個文件名和一個標誌。

A.文件名是我們之前接觸過的動態庫的名字,如果它是一個絕對路徑,如:/home/cyg/worddir/libname.so,此時dlopen直接到指定的路徑下打開動態庫。
如果沒有指定路徑,僅僅指定了一個動態庫的名字,此時dlopen將它先後搜索elf文件的DT_RPATH段、環境變量LD_LIBRARY_PATH、/etc/ld.so.cache文件列表、/lib、/usr/lib目錄找到庫文件後將其載入內存。

B.標誌指明是否立刻計算庫的依賴性。

常常一個庫中還依賴別的庫,就是這個函數實現的時候,調用了別的庫函數。不是在這個庫中實現的函數我們稱爲位定義的符號。

如果將標誌 設置爲RTLD_NOW的話,則會在dlopen函數返回前,將這些未定義的符號解析出來。如果設置爲RTLD_LAZY,則會在需要的時候纔會去解析。

返回值:dlopen()函數會返回一個句柄作爲dlsym()函數的第一個參數,以獲得符號在庫中的地址。使用這個地址,就可以獲得庫中特定函數的指針,並且調用裝載庫中的相應函數。

<2>dlerror()

當動態鏈接庫操作函數執行失敗時,dlerror可以返回出錯信息,返回值爲NULL時表示操作函數執行成功。

<3>void *dlsym(void *handle,char *symbol);

dlsym根據動態鏈接庫操作句柄(handle)與符號(symbol),返回符號對應的函數的執行代碼地址。由此地址,可以帶參數執行相應的函數。

如程序代碼 :int (*add)(int x,int y);//函數指針

handle = dlopen("xxx.so",RTLD_LAZY);//打開共享庫
add = dlsym(handle,"add");//獲取add函數在共享庫的地址
value = add(12,34);//調用add函數

<4>int dlclose(void *handle);
dlclose用於關閉指定句柄的動態鏈接庫,只有當此動態鏈接庫的使用計數爲0時,纔會真正被系統卸載。

案例:
#include 
#include 
#include 
#include 

int test_dl(char *pso,char *pfu)
{
void *handle;
int (*ptest)(int x,int y);

if((handle = dlopen(pso,RTLD_LAZY)) == NULL)
{
printf("%s.\n",dlerror());
return -1;
}
if((ptest = dlsym(handle,pfu)) == NULL)
{
printf("%s.\n",dlerror());
return -1;
}

printf("ptest complete : %d.\n",ptest(12,13));
dlclose(handle);

return 0;
}

int main(int argc,char *argv[])
{
char buf[100];
char *pso,*pfun;
printf("Input xxx.so:function_name.\n");
while(1)
{
printf(">");
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1] = 0;

pso = strdup(strtok(buf,":"));
pfun = strdup(strtok(NULL,":"));
test_dl(pso,pfun);
}
return 0;
}

運行結果:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章