目錄
1、連結庫概述
2、靜态連結庫
2.1、編輯測試檔案
2.2、将.c 編譯生成 .o檔案
2.3、由 .o 檔案建立.a靜态庫
2.4、在程式中使用靜态庫
2.5、生成目标程式main,然後運作
3、動态庫(隐式連結)
3.1、由 .o建立.so動态庫
3.2、隐式方式使用動态庫
3.3、動态庫的初始化和解析
1、動态庫的構造和析構函數機制
2、全局變量初始化
4、動态連結庫(顯式連結)
4.1、重要的dlfcn.h頭檔案
4.2、顯加載示動态連結庫的執行個體
4.3、Windows下和Linux下顯示加載動态連結庫的比較
5、特殊情況
6、檢視庫中的符号
1、使用nm指令可以列印出庫中涉及到的所有符号。庫既可以是靜态庫也可以是動态的
7、Linux下so導出指定函數
1、連結庫概述
Linux下得庫有動态與靜态兩種,動态通常用.so為字尾,靜态用.a為字尾。面對比一下兩者:
靜态連結庫:當要使用時,連接配接器會找出程式所需的函數,然後将它們拷貝到執行檔案,由于這種拷貝是完整的,是以一旦連接配接成功,靜态程式庫也就不再需要了。
動态庫而言:某個程式在運作中要調用某個動态連結庫函數的時候,作業系統首先會檢視所有正在運作的程式,看在記憶體裡是否已有此庫函數的拷貝了。如果有,則讓其共享那一個拷貝;隻有沒有才連結載入。在程式運作的時候,被調用的動态連結庫函數被安置在記憶體的某個地方,所有調用它的程式将指向這個代碼段。是以,這些代碼必須使用相對位址,而不是絕對位址。在編譯的時候,我們需要告訴編譯器,這些對象檔案是用來做動态連結庫的,是以要用位址無關代碼(Position Independent Code (PIC))。
動态連結庫的加載方式有兩種:隐式加載和顯示加載。
注意:linux下進行連接配接的預設操作是首先連接配接動态庫,也就是說,如果同時存在靜态和動态庫,不特别指定的話,将與動态庫相連接配接(見本文第四部分)。
2、靜态連結庫
下面就通過實際的例子來向大家示範一下,該怎樣編譯和使用靜态和動态連結庫:
二個檔案:add.c、 sub.c、add.h 、sub.h 和 main.c
/*add.h */
#ifndef _ADD_H_
#define _ADD_H_
int add(int a, int b);
#endif
-------------------------------------------------------------------------------------------------
/*add.c*/
#include "add.h"
int add(int a, int b)
{
return a+b;
}
-------------------------------------------------------------------------------------------------
/*sub.h*/
#ifndef _SUB_H_
#define _SUB_H_
int sub(int a, int b);
#endif
-------------------------------------------------------------------------------------------------
/*sub.c*/
#include "add.h"
int sub(int a, int b)
{
return a-b;
}
-------------------------------------------------------------------------------------------------
/*main.c*/
#include <stdio.h>
#include "add.h"
#include "sub.h"
int main(void)
{
printf("1 + 2 =%d\n", add(1, 2));
printf("1 - 2 =%d\n", sub(1, 2));
return 0;
}
-------------------------------------------------------------------------------------------------
gcc -c add.c
gcc -c sub.c
生成的檔案:sub.o ,add.o
無論是靜态庫檔案還是動态庫檔案,都是由 .o 檔案建立的。
ar crlibmymath.a sub.o add.o
ar:靜态函數庫建立的指令
-c :create的意思
-r :replace的意思,表示目前插入的子產品名已經在庫中存在,則替換同名的子產品。如果若幹子產品中有一個子產品在庫中不存在,ar顯示一個錯誤資訊,并不替換其他同名的子產品。預設的情況下,新的成員增加在庫德結尾處。
庫檔案的命名規範是以lib開頭(字首),緊接着是靜态庫名,以 .a 為字尾名。
gcc -o main main.c -L. –lmymath
-L 指定函數庫查找的位置,注意L後面還有'.',表示在目前目錄下查找
-l則指定函數庫名,其中的lib和.a(.so)省略。
注意:-L是指定查找位置,-l指定需要操作的庫名。
靜态庫制作完了,如何使用它内部的函數呢?隻需要在使用到這些公用函數的源程式中包含這些公用函數的原型聲明,然後在用gcc指令生成目标檔案時指明靜态庫名(是mymath 而不是libmymath.a ),gcc将會從靜态庫中将公用函數連接配接到目标檔案中。注意,gcc會在靜态庫名前加上字首lib,然後追加擴充名.a得到的靜态庫檔案名來查找靜态庫檔案。在程式main.c中,我們包含了靜态庫的頭檔案add.h和sub.h,然後在主程式main中直接調用公用函數add()和sub()即可。
./main
1 + 2 = 3
1 - 2 = -1
動态庫檔案名命名規範和靜态庫檔案名命名規範類似,也是在動态庫名增加字首lib,但其檔案擴充名為.so。例如:我們将建立的動态庫名為mymath,則動态庫檔案名就是libmamath.so。用gcc來建立動态庫。在系統提示符下鍵入以下指令得到動态庫檔案libmamath.so。
gcc -fPIC-o add.o -c add.c
gcc -fPIC-o sub.o -c sub.c
gcc -shared-o libmamath.so add.o sub.o
或者:
gcc –c –o add.oadd.c
gcc –c –o sub.osub.c
gcc -shared -fPCI-o libmyhello.so add.o sub.o
這裡:
-fpic:産生代碼位置無關代碼
-shared :生成共享庫
在程式中隐式使用動态庫和使用靜态庫完全一樣,也是在使用到這些公用函數的源程式中包含這些公用函數的原型聲明,然後在用gcc指令生成目标檔案時指明動态庫名進行編譯。我們先運作gcc指令生成目标檔案,再運作它看看結果。
gcc -o main main.c -L. -lmymath
./main
./main: error while loading shared libraries:libmymath.so: cannot open shared object file: No such file or directory
出錯了!!!
快看看錯誤提示,原來是找不到動态庫檔案libmyhello.so。程式在運作時,會在/usr/lib和/lib等目錄中查找需要的動态庫檔案。若找到,則載入動态庫,否則将提示類似上述錯誤而終止程式運作。
動态庫的搜尋路徑搜尋的先後順序是:
1.編譯目标代碼時指定的動态庫搜尋路徑;
2.環境變量LD_LIBRARY_PATH指定的動态庫搜尋路徑;
3.配置檔案/etc/ld.so.conf中指定的動态庫搜尋路徑;//隻需在在該檔案中追加一行庫所在的完整路徑如"/root/test/conf/lib"即可,然後ldconfig是修改生效。
4.預設的動态庫搜尋路徑/lib;
5.預設的動态庫搜尋路徑/usr/lib。
為此解決方法:
1. 我們将檔案libmyhello.so複制到目錄/usr/lib中:
mv libmyhello.so/usr/lib/
2. 将libmyhello.so拷貝到可執行檔案main的同一目錄下。
再次運作:./main
1 + 2 = 3
1 - 2 = -1
成功了!這也進一步說明了動态庫在程式運作時是需要的。
Windows下的動态庫加載,解除安裝都會有初始化函數以及解除安裝函數來完成庫的初始化以及資源回收,linux當然也可以實作,這些初始化函數主要包含兩個部分:動态庫的構造和析構函數機制、動态庫的全局變量初始化工作。
在Linux中,提供了一個機制:在加載和解除安裝動态庫時,可以編寫一些函數,處理一些相應的事物,我們稱這些函數為動态庫的構造和析構函數,其代碼格式如下:
void __attribute__ ((constructor)) my_init(void); // my_init為自定義的構造函數名
void __attribute__ ((destructor)) my_fini(void); //my_fini為自定義的析構函數名
在編譯共享庫時,不能使用"-nonstartfiles"或"-nostdlib"選項,否則建構與析構函數将不能正常執行(除非你采取一定措施)。
注意,構造函數的參數必須為空,傳回值也必須為空。
舉個例子,動态庫檔案a.c的代碼如下:
void __attribute__((constructor)) my_init(void)
{
printf("init library\n");
}
編譯成動态庫:
gcc -fPIC -shared a.c -o liba.so
主程式main.c如下:
#include<stdlib.h>
#include<stdio.h>
int main()
{
pause();
return 0;
}
編譯:
gcc -L./ -la main.c -o main.bin
運作main.bin程式:
也就是說,在運作main時,加載完liba.so後,自動運作liba.so的初始化函數。
①先看如下例子:
//檔案名:b1.c
#include<stdlib.h>
#include<stdio.h>
int reti()
{
printf("reti\n");
return 10;
}
int g1=reti(); // g1是個全局變量
使用GCC對其進行編譯:
gcc -fPIC -shared b1.c -o libb.so
編譯錯誤!使用G++對其進行編譯:
g++ -fPIC -shared b1.c -o libb.so
編譯成功!可見GCC和G++對于這種全局變量初始化的方法,支援力度是不一樣的。
//主程式
//檔案名:main.c
#include <stdlib.h>
#include <stdio.h>
int main()
{
pause();
return 0;
}
編譯執行檔案:
gcc -L./ -lb main.c -o main.bin
運作main.bin:
這說明,程序在加載libb.so後,為了初始化全局變量g1,其會運作reti來初始化g1。
②再來看一個C++的例子:
//檔案名:b2.cpp
class Myclass
{
public:
Myclass();
int i;
};
Myclass::Myclass()
{
printf("constructMyclass\n");
};
Myclass g1;
編譯動态庫:
g++ -fPIC -shared b2.cpp-o libb.so
在動态庫libb.so中,聲明了一個類型為Myclass的全局變量g1。
//主程式
//檔案名:main.cpp
#include <stdlib.h>
#include <stdio.h>
#include<unistd.h>
int main()
{
pause();
return 0;
}
g++ -L./ -lb main.cpp -o main.bin
這說明,程序在加載liba.so後,為了初始化全局變量g1,程式在進入main函數前将會運作Myclass的構造函數。
LINUX下使用動态連結庫,源程式需要包含dlfcn.h頭檔案,此檔案定義了調用動态連結庫的函數的原型。下面詳細說明一下這些函數。
函數dlerror:
原型為: const char *dlerror(void);
當動态連結庫操作函數執行失敗時,dlerror可以傳回出錯資訊,傳回值為NULL時表示操作函數執行成功。
函數dlopen:打開指定的動态連結庫檔案
原型為: void *dlopen (const char *filename, int flag);
dlopen用于打開指定名字(filename)的動态連結庫,并傳回操作句柄。
filename: 如果名字不以/開頭,則非絕對路徑名,将按下列先後順序查找該檔案:
(1) 使用者環境變量中的LD_LIBRARY值;
(2) 動态連結緩沖檔案/etc/ld.so.cache
(3) 目錄/lib,/usr/lib
flag表示在什麼時候解決未定義的符号(調用)。取值有兩個:
1) RTLD_LAZY : 表明在動态連結庫的函數代碼執行時解決。
2) RTLD_NOW : 表明在dlopen傳回前就解決所有未定義的符号,一旦未解決,dlopen将傳回錯誤。
dlopen調用失敗時,将傳回NULL值,否則傳回的是操作句柄。
函數dlsym : 取函數執行位址
原型為: void *dlsym(void *handle, char *symbol);
dlsym根據動态連結庫操作句柄(handle)與符号(symbol),傳回符号對應的函數的執行代碼位址。由此位址,可以帶參數執行相應的函數。
如程式代碼: void (*add)(int x,int y); /* 說明一下要調用的動态函數add */
add=dlsym("xxx.so","add"); /* 打開xxx.so共享庫,取add函數位址 */
add(89,369); /* 帶兩個參數89和369調用add函數 */
函數dlclose : 關閉動态連結庫
原型為: int dlclose (void *handle);
dlclose用于關閉指定句柄的動态連結庫,隻有當此動态連結庫的使用計數為0時,才會真正被系統解除安裝。
在下面這個執行個體中将通過動态加載libmymath.so連結庫,來調用add()和sub()兩個函數。
/*main.c*/
#include <stdio.h>
#include <dlfcn.h>
int main(void)
{
void*dp=dlopen("libmymath.so",RTLD_LAZY);
if(NULL==dp)
{
printf("打開動态連結庫時失敗!");
return1;
}
//定義函數指針
int(*fn_add)(int,int)=NULL;
int(*fn_sub)(int,int)=NULL;
fn_add=dlsym(dp,"add");
fn_sub=dlsym(dp,"sub");
if(NULL==fn_add|| NULL==fn_sub)
{
printf("在動态連結庫中尋找函數失敗!");
return1;
}
printf("1+ 2 = %d\n", fn_add(1, 2));
printf("1- 2 = %d\n", fn_sub(1, 2));
dlclose(dp);
return0;
}
将libmymath.so和main.c放在同一個目錄下,執行如下指令:
gcc -rdynamic -s -o main.bin main.c -ldl
-rdynamic選項以指定輸出檔案為動态連結的方式
-s指定删除目标檔案中的符号表,
-ldl則訓示裝配程式ld需要裝載dl函數庫。
最後運作main.bin的結果同上。
Windows下動态連結庫以“.dll”為字尾,而Linux下得動态連結庫是以”.so”為字尾的。
函數功能 | Windows下 | Linux下 |
打開加載動态連結庫 | LoadLibrary | dlopen |
擷取動态連結庫中的函數位址 | GetProcAddress | dlsym |
關閉動态連結庫 | FreeLibrary | dlclose |
在使用時應包含的頭檔案 | Winbase.h(include Windows.h) | dlfcn.h |
5、特殊情況
我們回過頭看看,發現使用靜态庫和隐式方式使用動态庫時編譯成目标程式使用的gcc指令完全一樣,那當靜态庫和動态庫同名時,gcc指令會使用哪個庫檔案呢?抱着對問題必究到底的心情,來試試看。先删除除.c和.h外的所有檔案,恢複成我們剛剛編輯完舉例程式狀态。
gcc -shared -fPCI -olibmyhello.so sub.o add.o
現在目錄有兩個同名的庫檔案(動态庫檔案和靜态庫檔案同名):
libmymath.a 、 libmymath.so
編譯運作程式:
gcc -o main main.c -L. -lmymath
./main: error while loading shared libraries:libmymath.so: cannot open shared object file: No such file or directory
從程式./main運作的結果中很容易知道,當Linux靜态庫和Linux動态庫同名時, gcc指令将優先使用動态庫。如果強制使用靜态庫則需要加-static選項支援,即:
gcc-static -o main main.c -L. -lmymath
連結靜态庫的可執行程式明顯比連結動态庫的可執行檔案大。
常見的三種符号:
①在庫中被調用,但沒有在庫中定義(表明需要其他庫支援),用U表示
②在庫中定義的函數,用T表示
③“弱态”符号,他們雖然在庫中被定義但是可能被其他庫中同名的符号覆寫,用W表示。
2、用ldd指令可以檢視一個可執行程式依賴的共享庫。
7、Linux下so導出指定函數
Linux下編譯so導出源檔案裡面的指定函數:
1、在檔案裡面最前面加上:#defineDLL_PUBLIC __attribute__((visibility("default")))