Linux手機DIY.庫檔案專題.知識準備
草木瓜轉抄 於 2006-11-9
一、序
軟體移植過程中,Linux作業系統的庫檔案著實令人頭疼,這方面資料
也比較少。通過一段時間搜尋查詢推敲,寫點總結吧,也算是有點成果。不過
這篇內容大多都是抄的,這裡對原創作者表示深深的敬意~
二、重要提示
為了方便更好的理解本文,提供下面鏈結。
全系列的文章地址,手機應用開發專欄:http://blog.csdn.net/liwei_cmg
相關的重要成果的:http://play.younet.com/view.php?tid=24045
三、Linux的庫檔案格式
[以下內容整理自《建立和使用庫》一文,作者不詳]
C語言中有一些函數不需要進行編譯,也可以在多個檔案中使用。
一般來說,有此函數會執行一定的標準任務,如資料庫輸入/輸出操作或
螢幕控制等。可以事先對這些函數進行編譯,然後將它們放置在一些特殊的目
標代碼檔案中,這些目標代碼檔案就稱為庫。
庫檔案中的函數可以通過串連程式與應用程式進行串連。這樣就不必在每
次開發程式時都對這些通用的函數進行編譯了。
不同類型的應用程式會使用不同的函數庫。例如:libdbm庫中組包含了對
資料庫檔案進行訪問的dbm函數,需要對資料庫進行操作的程式就會與該庫進
行串連。數學應用程式使用數學庫libm,X-Windows應用程式使用Xlib庫,
libX11。另外,所有的程式都將使用標準的C函數庫。libc,該庫中包含了內
存管理或輸入輸出操作的基本函數,這些庫都存放在/usr/lib這些系統公用
的目錄中,系統中的任何使用者都可以利用這些庫。當然使用者也可以建立自己
專用的庫函數,供自己或其它指定的人員使用。
庫可以有三種使用的形式:靜態、共用和動態。靜態庫的代碼在編譯時間就
已串連到開發人員開發的應用程式中,而共用庫只是在程式開始運行時才載入,
在編譯時間,只是簡單地指定需要使用的庫函數。動態庫則是共用庫的另一種變
化形式。動態庫也是在程式運行時載入,但與共用庫不同的是,使用的庫函數
不是在程式運行開始,而是在程式中的語句需要使用該函數時才載入。動態庫
可以在程式運行期間釋放動態庫所佔用的記憶體,騰出空間供其它程式使用。由
於共用庫和動態庫並沒有在程式中包括庫函數的內容,只是包含了對庫函數的
引用,因此代碼的規模比較小。
已經開發的大多數庫都採取共用庫的方式。ELF格式的可執行檔使得共用
庫能夠比較容易地實現,當然使用舊的a.out模式也可以實現庫的共用。Linux
系統中目前可執行檔的標準格式為ELF格式。
GNU庫的使用必須遵守Library GNU Public License(LGPL許可協議)。該
協議與GNU許可協議略有不同,開發人員可以免費使用GNU庫進行軟體開發,但
必須保證向使用者提供所用的庫函數的原始碼。
系統中可用的庫都存放在/usr/lib和/lib目錄中。庫檔案名稱由首碼lib和
庫名以及尾碼組成。根據庫的類型不同,尾碼名也不一樣。共用庫的尾碼名
由.so和版本號碼組成,靜態庫的尾碼名為.a。採用舊的a.out格式的共用庫的
尾碼名為.sa。
使用gcc編譯器就可以將庫與自己開發的程式串連起來,例如:libc.so.5
中包含了標準的輸入輸出函數,當串連程式進行目標代碼串連時會自動搜尋該
程式並將其串連到產生的可執行檔中。標準的輸入輸出庫中包含了許多基本
的輸入輸出函數,如printf函數等。也可以串連其它的一些系統函數庫,如數
學庫等,但與libc.so.5不同,大部分其它的系統庫需要在命令列中顯式指定
所用的庫名。
在/usr/lib和/lib目錄中可以找到絕大多數的共用庫。串連時將首先搜尋
這兩個目錄。有一些庫也可能存放在特定的目錄中,在/etc/ld.conf設定檔
中給出了這些目錄的列表。串連程式也會對列出的這些目錄進行搜尋。在預設
情況下,Linux將首先搜尋指定庫的共用版本,如果找不到,才會去搜尋靜態
版本。在對共用庫進行更新或安裝新庫後,必須運行ldconfig命令更新
/etc/ld.conf檔案中相應的項(如果使用RPM進行安裝,一般會自動進行更新,
不過也不能保證這一點)。
在gcc編譯器中引用可搜尋到的目錄中的庫檔案時,需要使用-l選項和庫
名。在gcc命令列上輸入-lm可以在程式中串連標準算術庫,-l將首先使用
libname.so進行搜尋,這裡是libm.so。下面的例子將使用算術庫建立bookrecs
程式,請注意這裡的-lm選項。
$ gcc main.c io.c -o bookrecs -lm
系統中還有一些其它可用的庫,常用的是libncurses.a庫,包含了一些簡
單的滑鼠移動常式。在命令列中使用-lncurses選項引用libncurses.so庫。下
面的例子同時調用了數學和游標庫。
$ gcc mian.c io.c -o bookrecs -lm -lncurses
在引用其它目錄中的庫時,需要使用-ldir選項指定該目錄。該選項指定
了搜尋庫函數時其它路徑。在下面的例子中,使用者在串連時使用了mydir目錄
中的myio.so庫檔案。
$ gcc main.c -o bookrecs -lmydir -lmyio
Linux下檔案的類型是不依賴於尾碼名的,但一般來講:
.o 為目標檔案,類似於windows中的.obj檔案
.so 為共用庫檔案,用於動態串連,類似於dll檔案
.a 為靜態庫,是多個.o檔案合在一起,用於靜態串連
.la 為libtool自動產生的一些共用庫,可用vi編輯查看,主要記錄了一
些配置資訊。
四、建立和使用Linux庫檔案
[以下內容整理自《Linux靜態/動態連結程式庫的建立和使用》一文,
http://blog.csdn.net/hcj2002/]
和Windows系統一樣Linux也有靜態/動態連結程式庫,下面介紹建立和使用
方法:
假設有下面幾個檔案:
標頭檔String.h,聲明相關函數原形,內容如下:
Strlen.c:函數Strlen的實現,擷取給定字串的長度,內容如下:
Strlnen.c:函數StrNlen的實現,擷取給定字串的長度,如果輸入字
符串的長度大於指定的最大長度,則返回最大長度,否者返回字串的實際
長度,內容如下:
產生靜態庫:
利用GCC產生對應目標檔案:
gcc –c Strlen.c Strnlen.c
如果對應的檔案沒有錯誤,gcc會對檔案進行編譯產生Strlen.o和Strnlen.o
兩個目標檔案(相當於windows下的obj檔案)。然後用ar建立一個名字為libstr.a
的庫檔案,並把Strlen.o 和Strnlen.o的內容插入到對應的庫檔案中。相關命
令如下:
ar –rc libstr.a Strlen.o Strnlen.o
命令執行成功以後,對應的靜態庫libstr.a已經成功產生。
/***********************************
Filename : String.h
Description :
Author : HCJ
Date : 2006-5-7
************************************/
int Strlen(char *pStr);
int StrNlen(char *pStr, unsigned long ulMaxLen);
/**************************************
Filename : get string length
Description :
Author : HCJ
Date : 2006/5/7
**************************************/
#include<stdio.h>
#include<assert.h>
int Strlen(char *pStr)
{
unsigned long ulLength;
assert(NULL != pStr);
ulLength = 0;
while(*pStr++)
{
ulLength++;
}
return ulLength;
}
**********************************************
Fileneme: mystrnlen.c
Description: get input string length,if string large
max length input return max length,
else real length
Author: HCJ
Date : 2006-5-7
**********************************************/
#include<stdio.h>
#include<assert.h>
int StrNlen(char *pStr, unsigned long ulMaxLen)
{
unsigned long ulLength;
assert(NULL != pStr);
if(ulMaxLen <= 0)
{
printf("Wrong Max Length!/n");
return -1;
}
ulLength = 0;
while(*pStr++ && ulLength < ulMaxLen)
{
ulLength++;
}
return ulLength;
}
產生動態連結程式庫:
gcc -fpic -shared -o libstr.so Strlen.c Strnlen.c
-fpic 使輸出的對象模組是按照可重定位地址方式產生的。
-shared指定把對應的源檔案產生對應的動態連結程式庫檔案libstr.so
檔案。
對應的連結庫已經產生,下面看一下如何使用對應的連結庫。
靜態庫的使用:
假設有下面的檔案要使用對應的的靜態庫:
編譯產生對應的目標檔案:
gcc -c -I/home/hcj/xxxxxxxx main.c
產生可執行檔:
gcc -o main1 -L/home/hcj/xxxxxxxx main.o libstr.a
其中-I/home/hcj/xxxxxxxx和-L/home/hcj/xxxxxxxx是通過-I和-L指
定對應的標頭檔和庫檔案的路徑。libstr.a是對應的靜態庫的名稱。這樣
對應的靜態庫已經編譯到對應的可執行程式中。執行對應的可執行檔便
可以對應得函數調用的結果。
/*****************************************
FileName: main.c
Description: test static/dynamic library
Author: HCJ
Date : 2005-5-7
******************************************/
#include<stdio.h>
#include <String.h> //靜態庫對應函數的標頭檔
int main(int argc, char* argv[])
{
char str[] = {"hello world"};
unsigned long ulLength = 0;
printf("The string is : %s/n", str);
ulLength = Strlen(str);
printf("The string length is : %d(use Strlen)/n", ulLength);
ulLength = StrNlen(str, 10);
printf("The string length is : %d(use StrNlen)/n", ulLength);
return 0;
}
動態庫的分為隱式調用和顯式調用兩種調用方法:
隱式調用的使用使用方法和靜態庫的調用差不多,具體方法如下:
gcc -c -I/home/hcj/xxxxxxxx main.c
gcc -o main1 -L/home/hcj/xxxxxxxx main.o libstr.so //這裡是*.so
在這種調用方式中,需要維護動態連結程式庫的設定檔/etc/ld.so.conf
來讓動態連結程式庫為系統所使用,通常將動態連結程式庫所在目錄名追加到動態鏈
接庫設定檔中。否則在執行相關的可執行檔的時候就會出現載入動態鏈
接庫失敗的現象。在編譯所引用的動態庫時,可以在gcc採用 –l或-L選項或
直接引用所需的動態連結程式庫方式進行編譯。在Linux裡面,可以採用ldd命令
來檢查程式依賴共用庫。
顯式調用:
/*****************************************
FileName: main2.c
Description: test static/dynamic library
Author: HCJ
Date : 2005-5-7
******************************************/
#include<stdio.h>
#include<dlfcn.h>
int main(int argc, char* argv[])
{
//define function pointor
int (*pStrlenFun)(char* pStr); //聲明對應的函數的函數指標
int (*pStrnlenFun)(char* pStr, int ulMaxLen);
char str[] = {"hello world"};
unsigned long ulLength = 0;
void *pdlHandle;
char *pszErr;
pdlHandle = dlopen("./libstr.so", RTLD_LAZY); //載入連結庫/libstr.so
if(!pdlHandle)
{
printf("Failed load library/n");
}
pszErr = dlerror();
if(pszErr != NULL)
{
printf("%s/n", pszErr);
return 0;
}
//get function from lib
pStrlenFun = dlsym(pdlHandle, "Strlen"); //擷取函數的地址
pszErr = dlerror();
if(pszErr != NULL)
{
printf("%s/n", pszErr);
return 0;
}
pStrnlenFun = dlsym(pdlHandle, "StrNlen");
pszErr = dlerror();
if(pszErr != NULL)
{
printf("%s/n", pszErr);
return 0;
}
printf("The string is : %s/n", str);
ulLength = pStrlenFun(str); //調用相關的函數
printf("The string length is : %d(use Strlen)/n", ulLength);
ulLength = pStrnlenFun(str, 10);
printf("The string length is : %d(use StrNlen)/n", ulLength);
dlclose(pdlHandle);
return 0;
}
gcc -o mian2 -ldl main2.c
用gcc編譯對應的源檔案產生可執行檔,-ldl選項,表示產生的對象模
塊需要使用共用庫。執行對應得檔案同樣可以得到正確的結果。
相關函數的說明如下:
(1)dlopen()
第一個參數:指定共用庫的名稱,將會在下面位置尋找指定的共用庫。
-環境變數LD_LIBRARY_PATH列出的用分號間隔的所有目錄。
-檔案/etc/ld.so.cache中找到的庫的列表,用ldconfig維護。
-目錄usr/lib。
-目錄/lib。
-目前的目錄。
第二個參數:指定如何開啟共用庫。
-RTLD_NOW:將共用庫中的所有函數載入到記憶體
-RTLD_LAZY:會推後共用庫中的函數的載入操作,直到調用dlsym()時
方載入某函數
(2)dlsym()
調用dlsym時,利用dlopen()返回的共用庫的phandle以及函數名稱作為
參數,返回要載入函數的入口地址。
(3)dlerror()
該函數用於檢查調用共用庫的相關函數出現的錯誤。
這樣我們就用簡單的例子說明了在Linux下靜態/動態庫的建立和使用。