linux 動態庫 顯式調用 與 隱式調用

1、介紹

動態庫是程序設計常用的技術,採用動態庫可以有效的減少程序大小,節省空間,提高效率,增加程序的可擴展性,便於模塊化管理。在Windows和Linux操作系統中都有動態庫的概念。Windows將其稱爲動態鏈接庫(Dynamic Link Library,DLL),其文件擴展名爲.dll,Linux稱其爲共享庫技術(Shared Library),相應的共享庫文件擴展名爲.so。

故名思義,動態庫在程序運行的時候被動態鏈接。但是在具體使用動態庫的時候卻有兩種不同的方式:隱式鏈接和顯式鏈接。隱式鏈接在編譯/鏈接階段完成,由編譯系統根據動態庫的頭文件和庫文件進行編譯和鏈接,從而確定待調用的函數原形和地址。顯式鏈接則是利用API函數實現加載和卸載共享庫,獲取帶調用函數地址,獲取錯誤信息等功能。

2、隱式鏈接舉例

(1)動態庫文件代碼:dl_func.c

#include
extern char name[];
int add(int a, int b)
{
        printf("calling add\n");
        printf("Hello, %s!\n", name);
        return a + b;
}

該文件中的add()函數計算兩個整數之和,並且打印外部變量的值,該外部變量由調用共享庫的事例程序定義。

(2)客戶端事例代碼:dl_demo1.c

#include
#include

int add(int a, int b);

char name[100];
int main(int argc, char *argv[]) {
        int a = 10, b = 20;
        int c = 0;

        strcpy(name, "NHN XDBMS");
        c = add(a, b);
        printf("%d + %d = %d\n", a, b, c);

        return 0;
}

該事例程序調用共享庫的中的add()函數計算兩數之後並打印,同時在事例程序中,給變量name賦值,以便在add()函數中打印。

(3)編譯與運行

編譯共享庫:

gcc -o libdl_func.so -fPIC -rdynamic -shared dl_func.c
選項-fPIC指示編譯器將代碼編譯成位置獨立的代碼,一般需要以程序文件共享其函數或變量給其他程序文件的代碼都應該以此選項進行編譯,選項-rdynamic指示編譯器所編譯/鏈接的爲共享庫程序文件。由於要使用外部變量,因此需要-shared選項,否則編譯器會拋出錯誤信息:undefined reference to `name',表示不能找到name變量。

編譯事例程序:

gcc -o dl_demo1 -L./ -ldl_func dl_demo1.c
選項-L./ 指示編譯器在當前目錄下尋找共享庫文件,-ldl_func指示需要的共享庫文件名爲libdl_func.so。

運行:

./dl_demo1

輸出:

calling add
Hello, NHN XDBMS!
10 + 20 = 30

3、顯式鏈接API函數

顯式鏈接主要涉及到4個API函數( dlopen , dlerror , dlsym 和 dlclose ),這些函數原形定義包含在dlfcn.h頭文件中。

(1)void *dlopen(const char *file, int mode);

該函數用來按照指定模式打開指定的共享庫,將其影射到內存中,並且返回句柄。
第一個參數:指定共享庫的名稱,將會在下面位置查找指定的共享庫。
-環境變量LD_LIBRARY_PATH列出的用分號間隔的所有目錄。
-文件/etc/ld.so.cache中找到的庫的列表,用ldconfig維護。
-目錄usr/lib。
-目錄/lib。
-當前目錄。
第二個參數:指定如何打開共享庫。
-RTLD_NOW:將共享庫中的所有函數加載到內存
-RTLD_LAZY:會推後共享庫中的函數的加載操作,直到調用dlsym()時方加載某函數

(2)void *dlsym(void *restrict handle, const char *restrict name);

該函數返回一個指向由name所確定的請求入口點的指針。調用dlsym時,利用dlopen()返回的共享庫的phandle以及函數/變量名稱作爲參數,返回要加載函數/變量的入口地址。

(3)char *dlerror(void);

dlerror 返回 NULL 或者一個指向描述最近錯誤的 ASCII 字符串的指針

(4)int dlclose(void *handle);

關閉句柄並且取消共享目標文件的映射 

4、顯式鏈接舉例

(1)動態庫文件代碼:dl_func.c

與隱式鏈接的代碼相同。

(2)客戶端事例代碼:dl_demo.c

#include
#include

char name[100];
int main(int argc, char *argv[]) {
        int a = 10, b = 20;
        int c = 0;
        void *dlh = NULL;
        int (*add)();

        strcpy(name, "NHN XDBMS");
        if((dlh = dlopen("libdl_func.so", RTLD_LAZY)) == NULL) {
                fprintf (stderr, "***DL ERROR: %s.\n", dlerror ());
                return 1;
        }
        if((add = (int (*)())dlsym(dlh, "add")) == NULL) {
                fprintf (stderr, "***DL ERROR: %s.\n", dlerror ());
                return 1;
        }

        c = add(a, b);
        printf("%d + %d = %d\n", a, b, c);
        dlclose(dlh);
        return 0;
}

該事例程序給變量name賦值,以便在add()函數中打印。程序利用dlopen函數加載共享庫libdl_func.so,利用dlclose關閉句柄,利用dlerror獲取錯誤信息,利用dlsym定位共享庫中的add函數,然後調用該函數執行加法計算。

(3)編譯與運行

編譯共享庫:

與前述共享庫編譯方法相同。

編譯事例程序:

gcc -o dl_demo -fPIC -ldl dl_demo.c
由於變量name需要被共享庫中的add()函數使用,因此必須使用選項-fPIC。選項-ldl指示編譯器需要用來到libdl.so庫文件。

運行:

./dl_demo

輸出:

與隱式鏈接事例的輸出相同。

5、其他

(1)如事例中所給出的,除了共享庫可以給別人使用外,共享庫也可以使用調用程序中的變量,如在共享庫中打印事例程序中的name。不過由於name在外部定義和聲明因此在鏈接共享庫時需要使用-shared選項。

(2)除了可以共享函數外,還可以共享變量,如果在dl_func.c中定義個變量:

int num = 100;

那麼可以在事例程序中這樣調用:

int *d;
d = (int *)dlsym(dlh, "num");
printf("num = %d\n", *d);


轉載來源: 

http://blog.sina.com.cn/s/blog_a4f2bd7d01016dy7.html


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章