2006年3月20日 星期一

Static 與 Shared 的函式庫撰寫

Standard
簡單來說函式庫分為兩種類型: Static﹝靜態﹞和 Shared﹝共享﹞,前者你可以想像是嵌入式,將函式嵌入到主程式中;後者是主程式能以 Dynamic (動態)方式指定呼叫的外部函式庫。

最近將一些程式模組化,把許多函式分門別類拉到主程式外做成函式庫模組。這工程雖不算浩大,但也挺累人的。因為函式庫的撰寫很有實用價值,我就隨手將它記下來,還有將以前所看過的文件重新翻出來復習一番。有一篇 《Program Library HOWTO》詳細記載了撰寫函式庫的方法和過程,有興趣的人可以參考。

撰寫程式時,程式碼裡的任何工作,扣除程式語法不算,總是由許許多多函式所組成,可別以為這些函式是電腦內建的,CPU可沒有這麼厲害,可以內建這樣龐大的指令集。這些函式都是由前人不斷發展提供的,可能是作業系統研發者,也可能是某應用程式的設計者。

一些公認標準的共用函式庫,讓一般程式設計師可以方便的使用各種功能,例如,當你想要在螢幕上顯示一行文字,你只要呼叫如 printf() 這種函式,而不必再去處理 低階的 BIOS Interrupt,然後控制 CPU 上的 Register 來達成螢幕輸出的工作。共用函式庫最大的好處,就是能讓許多工作不必重覆撰寫,以省下不少時間,更棒的是,你可以使用各種程式語言來呼叫使用這些函式。

文章一開始就提到函式庫分兩種類型:Static Library、Shared Library。

關於 Static Library ,應該大多程式設計者都很熟悉,就是將自定的函式寫再不同的 .c 檔中,在編譯時,透過編譯器去連結各個 .c 產生出來的二進位 .o 檔,將所有的函式都寫入同一執行檔中,使執行檔主程式能夠使用這些外部函式。

Shared Library 可就沒有這麼中央集權,我們可以將函式庫做成一個個的 .so 檔,你可以將它放入系統函式庫資料夾中,讓所有程式都能呼叫他。當你想要將自定的函式編譯成 Shared Library 時,可用以下指令:

摘錄於《Program Library - More Example

# 將自定函式的 .c 檔案編譯成 .o
gcc -fPIC -Wall -g -c libhello.c

# 將 .o 連結成 .so 檔
gcc -g -shared -Wl,-soname,libhello.so.0 -o libhello.so.0.0 libhello.o -lc

Shared Library 另一項特點就是可以動態載入,你不必在編譯時就指定使用,可以在程式中判斷是否存在進而動態載入,這樣的好處可用在模組化的程式,在需要使用某些功能時才載入必要的函式庫。如果要在你的程式中動態載入函式庫,要做以下兩步驟:

摘錄於《Program Library - More Example

1. 在你的程式中包含 dlfcn.h 並使用 dlopen() 等函式動態呼叫 Library

/* demo_dynamic.c -- demonstrate dynamic loading and
use of the "hello" routine */


/* Need dlfcn.h for the routines to
dynamically load libraries */
#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>

/* Note that we don't have to include "libhello.h".
However, we do need to specify something related;
we need to specify a type that will hold the value
we're going to get from dlsym(). */

/* The type "simple_demo_function" describes a function that
takes no arguments, and returns no value: */

typedef void (*simple_demo_function)(void);


int main(void) {
const char *error;
void *module;
simple_demo_function demo_function;

/* Load dynamically loaded library */
module = dlopen("libhello.so", RTLD_LAZY);
if (!module) {
fprintf(stderr, "Couldn't open libhello.so: %sn",
dlerror());
exit(1);
}

/* Get symbol */
dlerror();
demo_function = dlsym(module, "hello");
if ((error = dlerror())) {
fprintf(stderr, "Couldn't find hello: %sn", error);
exit(1);
}

/* Now call the function in the DL library */
(*demo_function)();

/* All done, close things cleanly */
dlclose(module);
return 0;
}



2. 在編譯時加上 -ldl
gcc -g -o demo_dynamic demo_dynamic.o -ldl

Library 的撰寫和規劃,在大系統的研發專案很好用,可以令各個功能的程式碼分開維護,而且當某一小功能更新,不必將整個專案程式都重新編譯,使整個系統研發流程更簡化。