dlopen函數詳解

接口概述 

Linux提供了一套API來動態裝載庫。下面列出了這些API:函數

    - dlopen,打開一個庫,併爲使用該庫作些準備。spa

    - dlsym,在打開的庫中查找符號的值。命令行

    - dlclose,關閉庫。指針

    - dlerror,返回一個描述最後一次調用dlopen、dlsym,或dlclose的錯誤信息的字符串。code

C語言用戶須要包含頭文件dlfcn.h才能使用上述API。glibc還增長了兩個POSIX標準中沒有的API- 接口

    -dladdr,從函數指針解析符號名稱和所在的文件。
    - dlvsym,與dlsym相似,只是多了一個版本字符串參數。

在Linux上,使用動態連接的應用程序須要和庫libdl.so一塊兒連接,也就是使用選項-ldl。可是,編譯時不須要和動態裝載的庫一塊兒連接。程序3-1是一個在Linux上使用dl*例程的簡單示例。
內存

延遲重定位(Lazy Relocation)

        延遲重定位/裝載是一個容許符號只在須要時才重定位的特性。這常在各UNIX系統上解析函數調用時用到。當一個和共享庫一塊兒連接的應用程序幾乎不會用到該共享庫中的函數時,該特性被證實是很是有用的。這種狀況下,只有庫中的函數被應用程序調用時,共享庫纔會被裝載,不然不會裝載,所以會節約一些系統資源。可是若是把環境變量LD_BIND_NOW設置成一個非空值,全部的重定位操做都會在程序啓動時進行。也能夠在連接器命令行經過使用-z now連接器選項使延遲綁定對某個特定的共享庫失效。須要注意的是,除非從新連接該共享庫,不然對該共享庫的這種設置會一直有效。資源

初始化(initializing)和終止化(finalizing)函數

      有時候,之前的代碼可能用到了兩個特殊的函數:_init和_fini。_init和_fini函數用在裝載和卸載某個模塊(註釋14)時分別控制該模塊的構造器和析構器(或構造函數和析構函數)。他們的C語言原型以下:
void _init(void);
void _fini(void);
        當一個庫經過dlopen()動態打開或以共享庫的形式打開時,若是_init在該庫中存在且被輸出出來,則_init函數會被調用。若是一個庫經過dlclose()動態關閉或由於沒有應用程序引用其符號而被卸載時,_fini函數會在庫卸載前被調用。當使用你本身的_init和_fini函數時,須要注意不要與系統啓動文件一塊兒連接。能夠使用GCC選項 -nostartfiles 作到這一點。
      可是,使用上面的函數或GCC的-nostartfiles選項並非很好的習慣,由於這可能會產生一些意外的結果。相反,庫應該使用__attribute__((constructor))和__attribute__((destructor))函數屬性來輸出它的構造函數和析構函數。以下所示:
void __attribute__((constructor)) x_init(void)
void __attribute__((destructor)) x_fini(void)

      構造函數會在dlopen()返回前或庫被裝載時調用。析構函數會在這樣幾種狀況下被調用:dlclose()返回前,或main()返回後,或裝載庫過程當中exit()被調用時。字符串

實例

咱們經過一個例子來說解dlopen系列函數的使用和操做:原型

主程序:

#include <stdlib.h>
#include <dlfcn.h>
#include <stdio.h>

//申明結構體
typedef struct __test {
    int i;
    void (* echo_fun)(struct __test *p);
}Test;

//供動態庫使用的註冊函數
void __register(Test *p) {
    p->i = 1;
    p->echo_fun(p);
}

int main(void) {

    void *handle = NULL;
    char *myso = "./mylib.so";

    if((handle = dlopen(myso, RTLD_NOW)) == NULL) {
        printf("dlopen - %sn", dlerror());
        exit(-1);
    }

    return 0;
}

動態庫:

#include <stdio.h>
#include <stdlib.h>

//申明結構體類型
typedef struct __test {
    int i;
    void (*echo_fun)(struct __test *p);
}Test;

//申明註冊函數原型
void __register(Test *p);

static void __printf(Test *p) {
    printf("i = %dn", p->i);
}

//動態庫申請一個全局變量空間
//這種 ".成員"的賦值方式爲c99標準
static Test config = {
    .i = 0,
    .echo_fun = __printf,
};

//加載動態庫的自動初始化函數
void _init(void) {
    printf("initn");
    //調用主程序的註冊函數
    __register(&config);
}

主程序編譯: gcc test.c -ldl -rdynamic

動態庫編譯: gcc -shared -fPIC -nostartfiles -o mylib.so mylib.c

主程序經過dlopen()加載一個.so的動態庫文件, 而後動態庫會自動運行 _init() 初始化函數, 初始化函數打印一個提示信息, 而後調用主程序的註冊函數給結構體從新賦值, 而後調用結構體的函數指針, 打印該結構體的值. 這樣就充分的達到了主程序和動態庫的函數相互調用和指針的相互傳遞.

gcc參數 -rdynamic 用來通知連接器將全部符號添加到動態符號表中(目的是可以經過使用 dlopen 來實現向後跟蹤).

gcc參數 -fPIC 做用: 當使用.so等類的庫時,當遇到多個可執行文件共用這一個庫時, 在內存中,這個庫就不會被複制多份,讓每一個可執行文件一對一的使用,而是讓多個可執行文件指向一個庫文件,達到共用. 宗旨:節省了內存空間,提升了空間利用率.

相關文章
相關標籤/搜索