A. 這類庫的名字一般是libxxx.a;利用靜態函數庫編譯成的文件比較大,因爲整個函數庫的所有數據都被整合進目標代碼中,他的優點就顯而易見了,即編譯後的執行程序不需要外部的函數庫支持,因爲所有使用的函數都已經被編譯進可執行文件了。當然這也會稱爲它的缺點,因爲如果靜態函數庫改變了,那麼你的程序必須重新編譯,而且體積也較大。
-r :replace的意思,表示當前插入的模塊名已經在庫中存在,則替換同名的模塊。如果若干模塊中有一個模塊在庫中不存在,ar顯示一個錯誤信息,並不替換其他同名的模塊。默認的情況下,新的成員增加在庫德結尾處。
-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;
}
運行結果: