靜態庫、共享庫、動態庫的建立和使用

一般庫分爲:靜態庫,共享庫,動態加載庫,。下面分別介紹。

1、 靜態庫:
1.概念:
    靜態庫就是一些目標文件的集合,以.a結尾。靜態庫在程序連接的時候使用,連接器會將程序中使用
    到函數的代碼從庫文件中拷貝到應用程序中。一旦連接完成,在執行程序的時候就不須要靜態庫了。
    因爲每一個使用靜態庫的應用程序都須要拷貝所用函數的代碼,因此靜態連接的文件會比較大。
2.建立與應用:
    首先建立庫文件libhello.c
    #include <stdio.h>
    void hello()
    {
        printf("hello, welcome to library world!\n");
    }
    建立頭文件libhello.h
    void hello();

    如今咱們建立libhello靜態庫文件:
    $ gcc -c libhello -o libhello.o
    $ ar rcs libhello.a libhello.o  
    其中ar中的rcs的意思是: r代表將模塊加入到靜態庫中,c表示建立靜態庫,s表示生產索引。
    
    咱們寫一個測試程序:
    $ cat test.c
    #include <stdio.h>
    int main(void)
    {
        printf("use library hello.\n");
        hello();
        return 0;
    }
    
    編譯與連接:
    $ gcc -c test.c -o test.o
    $ gcc test.o -L. -lhello -o test
    說明:-L.表示將當前目錄加入到庫搜索路徑。默認的庫搜索路徑在/usr/lib目錄下。
    另外這裏說明一下易混淆的參數-I, 它表示搜索頭文件的路徑。這樣gcc在查找頭文件的時候會首先
    到-I指定的目錄查找,而後纔是系統默認目錄。
    
    -l參數: -lname表示庫搜索目錄下的libname.a 或者libname.so文件 ,
    這也是爲何庫文件都以lib開頭的緣由之一。一個慣例嘛。固然了,若是你的庫文件不是
    libhello,而是hello. 那就不能用-l參數編譯了。 能夠這樣:
    gcc test.o -L. hello.a -o test
    
    注意: $gcc -L. -lhello test.o -o test 會出錯!。
    緣由是: -l是連接器選項,必需要放到被編譯文件的後面。 因此上面的命令中-lhello必定要
    放到 test.o的後面。

    運行:
    $ ./test
    use library hello.
    hello, welcome to library world!
    
    
2、共享庫:
一、共享庫的概念:
    共享庫以.so結尾. (so == share object) 在程序的連接時候並不像靜態庫那樣在拷貝
    使用函數的代碼,而只是做些標記。而後在程序開始啓動運行的時候,動態地加載所需模塊。因此,
    應用程序在運行的時候仍然須要共享庫的支持。 共享庫連接出來的文件比靜態庫要小得多。

二、共享庫的命名
    通常一個共享庫的有三個名字:soname, real-name, linker-name。下面先看實例:
    $ ls -l /usr/lib/libncurses*
    lrwxrwxrwx 1 root root     20 2008-05-25 13:54 libncurses.so -> /lib/libncurses.so.5
    lrwxrwxrwx 1 root root     13 2008-05-26 15:18 libncurses.so.5 -> libtermcap.so

    上面的libncurses.so.5就是soname, 其中ncurses是庫名,5分別是主版本號(major),
    固然也能夠有次版本號(minor)和發行號(release)。(相似於libncurses.so.5.0.0)
    .so固然表示共享庫了。一般soname只是real name的一個連接。
    而libtermcap.so 這是ncurse庫的real-name, 也就是包含真是代碼實現的文件.
    libncurses.so 則是linker name,用於應用程序連接的時候的一個搜索名。 它一般是soname的
    一個連接,形式爲libname.so

三、共享庫的裝載
    (1) 在全部基於GNU glibc的系統(固然包括Linux)中,在啓動一個ELF二進制執行程序時,
    一個特殊的程序"程序裝載器"會被自動裝載並運行。在linux中,這個程序裝載器就是
    /lib/ld-linux.so.X(X是版本號)。它會查找並裝載應用程序所依賴的全部共享庫。
    被搜索的目錄保存在/etc/ls.so.conf文件中,但通常/usr/local/lib並不在搜索之列,
    至少debian/ubuntu是這樣。這彷佛是一個系統失誤,只好本身加上了。固然,若是程序的每次啓動,
    都要去搜索一番,勢必效率不堪忍受。Linux系統已經考慮這一點,對共享庫採用了緩存管理。ldconfig
    就是實現這一功能的工具,其缺省讀取/etc/ld.so.conf文件,對全部共享庫按照必定規範創建
    符號鏈接,而後將信息寫入/etc/ld.so.cache。
     /etc/ld.so.cache的存在大大加快了程序的啓動速度。

    (2) 固然你也能夠經過設置環境變量LD_LIBRARY_PATH來設置ld的裝載路徑。這樣裝載器就會
    首先搜索該變量的目錄,而後纔是默認目錄。可是記住,LD_LIBRARY_PATH是用於開發和測試的,
    你能夠將一些用於測試的替代共享庫的目錄放到該變量中,相似於/etc/ld.so.preload的做用。
    可是該變量不該該用於正經常使用戶的正常程序。

    (3) 若是你不使用LD_LIBRARY_PATH環境變量,能夠經過以下方式給裝載器傳入路徑:
        $ /lib/ld-linux.so.2 --library-path PATH EXECUTABLE

四、共享庫的建立與應用
    (1) 建立共享庫:
    gcc    -fpic/fPIC -c source.c -o source.o
    gcc -shared -Wl,-soname,your_soname -o library_name file_list library_list
    說明:  -fpic或者-fPIC代表建立position independent code,這一般是建立共享庫必須的。
           -Wl 代表給連接器傳送參數,因此這裏-soname, library_name 爲給連接器的參數。
          -shared 代表是使用共享庫

    下面是使用a.c和b.c建立共享庫的示例:
      gcc -fPIC -g -c -Wall a.c
       gcc -fPIC -g -c -Wall b.c
       gcc -shared -Wl,-soname, libmyab.so.1 -o libmyab.so.1.0.1 a.o b.o -lc
    說明: lc == libc
    
    幾個須要注意的地方:
      a.不推薦使用strip處理共享庫,最好不要使用-fomit-frame-pointer編譯選項
      b.-fPIC和-fpic均可以產生目標獨立代碼,具體應用取決於平臺,-fPIC是always work,
      儘管其產生的目標文件可能會大些; -fpic產生的代碼小,執行速度快,但可能有平臺依賴限制。
      c.通常狀況下,-Wall,-soname,your_soname編譯選項是須要的。固然,-share選項更不能丟。

    (2) 安裝使用共享庫
    一旦你建立好共享庫後就須要安裝使用了,最簡單的辦法是將庫拷貝到默認目錄下(/usr/lib)。
    而後建立一些符號連接,最簡單的方式仍是使用ldconfig(8)來處理這裏符號連接。最後是從新
    編譯連接你的程序,經過-L和-l參數指定庫路徑就能夠了。

3、 動態加載庫
1. 概念
   動態加載庫(dynamically loaded (DL) libraries)是指在程序運行過程當中能夠加載的函數庫。
   而不是像共享庫同樣在程序啓動的時候加載。DL對於實現插件和模塊很是有用,由於他們能夠然程序在容許
   時等待插件的加載。在Linux中,動態庫的文件格式跟共享庫沒有區別,主要區別在於共享庫是運行時加載。
   有專門的一組API用於完成打開動態庫,查找符號,
   處理出錯,關閉動態庫等功能。
   
   下面對這些接口函數逐一介紹:
   (1) dlopen   
   函數原型:void *dlopen(const char *libname,int flag);
   功能描述:dlopen必須在dlerror,dlsym和dlclose以前調用,表示要將庫裝載到內存,準備使用。
   若是要裝載的庫依賴於其它庫,必須首先裝載依賴庫。若是dlopen操做失敗,返回NULL值;若是庫已經
   被裝載過,則dlopen會返回一樣的句柄。
   參數中的libname通常是庫的全路徑,這樣dlopen會直接裝載該文件;若是隻是指定了庫名稱,在dlopen
   會按照下面的機制去搜尋:
       a.根據環境變量LD_LIBRARY_PATH查找
       b.根據/etc/ld.so.cache查找
       c.查找依次在/lib和/usr/lib目錄查找。
   flag參數表示處理未定義函數的方式,可使用RTLD_LAZY或RTLD_NOW。RTLD_LAZY表示暫時不去
   處理未定義函數,先把庫裝載到內存,等用到沒定義的函數再說;RTLD_NOW表示立刻檢查是否存在未定義
   的函數,若存在,則dlopen以失敗了結。

   (2) dlerror
   函數原型:char *dlerror(void);
   功能描述:dlerror能夠得到最近一次dlopen,dlsym或dlclose操做的錯誤信息,返回NULL表示
   無錯誤。dlerror在返回錯誤信息的同時,也會清除錯誤信息。

   (3) dlsym
   函數原型:void *dlsym(void *handle,const char *symbol);
   功能描述:在dlopen以後,庫被裝載到內存。dlsym能夠得到指定函數(symbol)在內存中的位置(指針)。
   若是找不到指定函數,則dlsym會返回NULL值。但判斷函數是否存在最好的方法是使用dlerror函數,

   (4) dlclose
   函數原型:int dlclose(void *);
   功能描述:將已經裝載的庫句柄減一,若是句柄減至零,則該庫會被卸載。若是存在析構函數,則在dlclose
   以後,析構函數會被調用。   
   
2. 使用實例
    $cat dltest.c
    #include <stdlib.h>
    #include <stdio.h>
    #include <dlfcn.h>

    int main(int argc, char **argv)
    {
        void *handle;
        double (*cosine)(double);
        char *error;

        handle = dlopen ("/lib/libm.so.6", RTLD_LAZY);
        if (!handle) {
            fputs (dlerror(), stderr);
            exit(1);
        }

        cosine = dlsym(handle, "cos");
        if ((error = dlerror()) != NULL)  {
            fputs(error, stderr);
            exit(1);
        }

        printf ("%f\n", (*cosine)(2.0));
        dlclose(handle);
        return 0;

    }   

    
     編譯: $ gcc -o dltest dltest.c -ldl -Wall
     運行: $ ./dltest
           -0.416147


4、其餘
(1) nm命令能夠查可能一個庫中的符號linux

相關文章
相關標籤/搜索