天天看點

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