天天看點

linux so檔案的制作,Linux下動态連結庫*.so的編譯與使用(二)

程式清理

有一個C程式,用于實作算法和資料結構 (比如棧和相關的操作)。在同一個程式中,還有用于測試的main()函數,結構體定義,函數原型,typedef等等。

這樣的做法非常不“環保”。算法的實際運用和算法的實作混在一起。如果我想要重複使用之前的源程式,必須進行許多改動,并且重新編譯。最好的解決方案是實作子產品化: 隻保留純粹的算法實作,分離頭檔案,并編譯一個庫(library)。每次需要使用庫的時候(比如使用棧資料結構),就在程式中include頭檔案,連接配接庫。這樣,不需要每次都改動源程式。

如何在UNIX環境中建立共享庫 (shared library)。UNIX下,共享庫以so為字尾(shared object)。共享庫與Windows下的DLL類似,是在程式運作時動态連接配接。多個程序可以連接配接同一個共享庫。

#include

#include

typedef struct node *position;

typedef int ElementTP;

// point to the head node of the list

typedef struct node *STACK;

struct node {

ElementTP element;

position next;

};

STACK init_stack(void);

void delete_stack(STACK);

ElementTP top(STACK);

void push(STACK, ElementTP);

ElementTP pop(STACK);

int is_null(STACK);

void main(void)

{

ElementTP a;

int i;

STACK sk;

sk = init_stack();

push(sk, 1);

push(sk, 2);

push(sk, 8);

printf("Stack is null? %d\n", is_null(sk));

for (i=0; i<3; i++) {

a = pop(sk);

printf("pop: %d\n", a);

}

printf("Stack is null? %d\n", is_null(sk));

delete_stack(sk);

}

STACK init_stack(void)

{

position np;

STACK sk;

np = (position) malloc(sizeof(struct node));

np->next = NULL; // sk->next is the top node

sk = np;

return sk;

}

void delete_stack(STACK sk)

{

while(!is_null(sk)) {

pop(sk);

}

free(sk);

}

ElementTP top(STACK sk)

{

return (sk->next->element);

}

void push(STACK sk, ElementTP value)

{

position np, oldTop;

oldTop = sk->next;

np = (position) malloc(sizeof(struct node));

np->element = value;

np->next = sk->next;

sk->next = np;

}

ElementTP pop(STACK sk)

{

ElementTP element;

position top, newTop;

if (is_null(sk)) {

printf("pop() on an empty stack");

exit(1);

}

else {

top = sk->next;

element = top->element;

newTop = top->next;

sk->next = newTop;

free(top);

return element;

}

}

int is_null(STACK sk)

{

return (sk->next == NULL);

}

上面的main()部分是用于測試,不屬于功能子產品,在建立庫的時候應該去掉。

程式中的一些聲明,會被重複利用。比如:

typedef struct node *position;

typedef int ElementTP;

// point to the head node of the list

typedef struct node *STACK;

struct node {

ElementTP element;

position next;

};

STACK init_stack(void);

void delete_stack(STACK);

ElementTP top(STACK);

void push(STACK, ElementTP);

ElementTP pop(STACK);

int is_null(STACK);

這一段程式聲明了一些結構體和指針,以及棧操作的函數原型。當我們其他程式中調用庫時 (比如建立一個棧,或者執行pop操作),同樣需要寫這些聲明。我們把這些在實際調用中需要的聲明儲存到一個頭檔案mystack.h。在實際調用的程式中,可以簡單的include該頭檔案,避免了每次都寫這些聲明語句的麻煩。

經過清理後的C程式為mystack.c:

#include

#include

#include "mystack.h"

STACK init_stack(void)

{

position np;

STACK sk;

np = (position) malloc(sizeof(struct node));

np->next = NULL; // sk->next is the top node

sk = np;

return sk;

}

void delete_stack(STACK sk)

{

while(!is_null(sk)) {

pop(sk);

}

free(sk);

}

ElementTP top(STACK sk)

{

return (sk->next->element);

}

void push(STACK sk, ElementTP value)

{

position np, oldTop;

oldTop = sk->next;

np = (position) malloc(sizeof(struct node));

np->element = value;

np->next = sk->next;

sk->next = np;

}

ElementTP pop(STACK sk)

{

ElementTP element;

position top, newTop;

if (is_null(sk)) {

printf("pop() on an empty stack");

exit(1);

}

else {

top = sk->next;

element = top->element;

newTop = top->next;

sk->next = newTop;

free(top);

return element;

}

}

int is_null(STACK sk)

{

return (sk->next == NULL);

}

#include "..."; 語句将首先在工作目錄尋找相應檔案。如果使用gcc時,增加-I選項,将在-I提供的路徑中尋找。

制作.so檔案

我們的目标是制作共享庫,即.so檔案。

首先,編譯stack.c:

$gcc -c -fPIC -o mystack.o mystack.c

-c表示隻編譯(compile),而不連接配接。-o選項用于說明輸出(output)檔案名。gcc将生成一個目标(object)檔案mystack.o。

注意-fPIC選項。PIC指Position Independent Code。共享庫要求有此選項,以便實作動态連接配接(dynamic linking)。

生成共享庫:

$gcc -shared -o libmystack.so mystack.o

庫檔案以lib開始。共享庫檔案以.so為字尾。-shared表示生成一個共享庫。

這樣,共享庫就完成了。.so檔案和.h檔案都位于目前工作路徑(.)。

使用共享庫

我們編寫一個test.c,來實際調用共享庫:

#include

#include "mystack.h"

void main(void)

{

ElementTP a;

int i;

STACK sk;

sk = init_stack();

push(sk, 1);

push(sk, 2);

push(sk, 8);

printf("Stack is null? %d\n", is_null(sk));

for (i=0; i<3; i++) {

a = pop(sk);

printf("pop: %d\n", a);

}

printf("Stack is null? %d\n", is_null(sk));

delete_stack(sk);

}

編譯上述程式。編譯器需要知道.h檔案位置。

對于#include "...",編譯器會在目前路徑搜尋.h檔案。你也可以使用-I選項提供額外的搜尋路徑,比如-I/home/vamei/test。

對于#include <...>,編譯器會在預設include搜尋路徑中尋找。

編譯器還需要知道我們用了哪個庫檔案,在gcc中:

使用-l選項說明庫檔案的名字。這裡,我們将使用-lmystack (即libmystack庫檔案)。

使用-L選項說明庫檔案所在的路徑。這裡,我們使用-L. (即.路徑)。

如果沒有提供-L選項,gcc将在預設庫檔案搜尋路徑中尋找。

你可以使用下面的指令,來獲知自己電腦上的include預設搜尋路徑:

$gcc -print-prog-name=cc1-v

獲知庫預設搜尋路徑:

$gcc -print-search-dirs

我們所需的.h和.so檔案都在目前路徑,是以,使用如下指令編譯:

$gcc -o test test.c -lmystack -L.

将生成test可執行檔案。

$./test執行程式

運作程式

./test: error while loading shared libraries: libmystack.so: cannot open shared object file: No such file or directory

這是因為作業系統無法找到庫。libmystack.so位于目前路徑,位于庫檔案的預設路徑之外。盡管我們在編譯時(compile time)提供了.so檔案的位置,但這個資訊并沒有寫入test可執行檔案(runtime)。可以使用下面指令測試:

$ldd test

ldd用于顯示可執行檔案所依賴的庫。顯示:

linux-vdso.so.1 => (0x00007fff31dff000)

libmystack.so => not found

libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fca30de7000)

/lib64/ld-linux-x86-64.so.2 (0x00007fca311cb000)

這說明test可執行檔案無法找到它所需的libmystack.so庫檔案。

為了解決上面的問題,我們可以将.so檔案放入預設搜尋路徑中。但有時,特别是多使用者環境下,我們不享有在預設搜尋路徑寫入的權限。

一個解決方案是設定LD_LIBRARY_PATH環境變量。比如:

$export LD_LIBRARY_PATH=.

這樣,可執行檔案執行時,作業系統将在先在LD_LIBRARY_PATH下搜尋庫檔案,再到預設路徑中搜尋。環境變量的壞處是,它會影響所有的可執行程式。如果我們在編譯其他程式時,如果我們不小心,很可能導緻其他可執行檔案無法運作。是以,LD_LIBRARY_PATH環境變量多用于測試。

另一個解決方案,即提供-rpath選項,将搜尋路徑資訊寫入test檔案(rpath代表runtime path)。這樣就不需要設定環境變量。這樣做的壞處是,如果庫檔案移動位置,我們需要重新編譯test。使用如下指令編譯test.c:

$gcc -g -o test test.c -lmystack -L. -Wl,-rpath=.

-Wl表示,-rpath選項是傳遞給連接配接器(linker)。

test順利執行的結果為:

Stack is null? 0

pop: 8

pop: 2

pop: 1

Stack is null? 1