1. 程式人生 > >【ubuntu】linux鏈接庫

【ubuntu】linux鏈接庫

元素 動態庫 jpeg bash ria 其中 col cache instance

C標準庫和C++的STL是共享元件的例子,可以被我們的程序所鏈接。這樣的好處是:每個對象文件在鏈接時不需要被陳述,因為開發者可以批量引用庫。這簡化了應用之間的元素共享和重復利用。

庫類型

  1. 靜態庫(.a)
  2. 動態庫(.so):這種類型的庫只有一種形式,但是有兩種使用方式:
    1. 在運行時動態鏈接: 這種庫必須在編譯/鏈接階段可用。 這些共享的對象並不被包含到可執行元件中去,但被綁定到可執行程序。
    2. 動態加載/卸載,在執行時鏈接(例如:瀏覽器插件)。

庫命名傳統: 通常帶有lib前綴,所有C標準庫都是滿足這樣的規律。當鏈接時,命令行引用這個庫時將不包含前綴或者後綴;例如,gcc src-file.c -lm -lpthread

這個編譯、鏈接命令,在鏈接過程中引用的庫是數學庫m和線程庫pthread,這兩個庫可以在/usr/lib/libm.a/usr/lib/libpthread.a找到。註意:GNU編譯器現在有命令行-pthread選項,而老版本的編譯器需要通過-lpthread顯式指定線程庫。因此,你更有可能看到gcc src-file.c -lm -pthread

靜態庫(.a)

命令:cc -Wall -c ctest1.c ctest2.c

  • 選項-Wall:包含警告,可以看到警告的幫助頁
  • 創建一個libctest.a庫:ar -cvq libctest.a ctest1.o ctest2.o
  • 列出庫所包含的文件:ar -t libctest.a
  • 鏈接這個庫:
    • cc -o exe prog.c libctest.a
    • cc -o exe prog.c -L/path/to/library-dir -lctest

註意:當創建庫以後,通過命令行鏈接並生成可執行程序以後,會在可執行程序的archive中創建一個符號表,可執行程序就嵌入了ar命令,對於MS/Windows開發者來說,.a庫和Visual C++的.lib庫一樣。

動態鏈接的共享對象庫(.so)

生成一個共享庫方法:(動態鏈接對象庫文件)

  1. 創建一個對象代碼
  2. 創建庫
  3. 可選:使用符號鏈接創建默認版本

創建庫實例

gcc -Wall -fPIC -c *.c
gcc -shared -W1,-soname,libctest.so.1 -o libctest.so.1.0 *.o
mv libctest.so.1.o /opt/lib
ln -sf /opt/lib/libctest.so.1.0 /opt/lib/libctest.so.1
ln -sf /opt/lib/libctest.so.1.0 /opt/lib/libctest.so

這會創建一個libctest.so.1.o和一個指向它的符號鏈接。將鏈接級聯也是合法的:

ln -sf /opt/lib/libctest.so.1.0 /opt/lib/libctest.so.1
ln -sf /opt/lib/libctest.so.1 /opt/lib/libctest.so

如果查看/lib和/usr/lib下的所有庫,以上兩種方法都存在,linux開發者並沒有統一。重要的是符號鏈接最終指向的真實庫文件。
-Wall: 包含警告;
-fPIC:編譯器輸出獨立代碼位置,共享庫需要的一個特點;
-shared:產生一個共享庫對象,可以被其他對象鏈接形成一個可執行程序;
-Wl:可選,傳遞選項給鏈接器;在上面的例子裏,“-soname libctest.so.1被傳遞; -o:運算輸出,共享對象的名字為libctest.so.1.0`

庫的鏈接:

  • 鏈接/opt/lib/libctest.so 允許編譯-lctest執行;
  • 鏈接/opt/lib/libctest.so.a`允許運行時綁定運行;

編譯main程序並鏈接共享對象庫

gcc -Wall -I/path/to/include-flies -L/path/to/libraries prog.c -lctest -o prog
gcc -Wall -L/opt/lib prog.c -lctest -o prog
共享庫的名字是libctest.so,這就是為什麽你需要創建符號鏈接,否則你會得到/usr/bin/ld:cannot find -lctest 錯誤。這些庫不會被包含到可執行程序中,在運行過程中動態鏈接。

依賴列表

可執行程序依賴的共享庫可以通過`ldd name-of-executable列舉出來,當庫被加載時,共享連接庫中未解析錯誤可能會導致程序運行錯誤。
例如,

  [prompt]$ ldd libname-of-lib.so
        libglut.so.3 => /usr/lib64/libglut.so.3 (0x00007fb582b74000)
        libGL.so.1 => /usr/lib64/libGL.so.1 (0x00007fb582857000)
        libX11.so.6 => /usr/lib64/libX11.so.6 (0x00007fb582518000)
        libIL.so.1 (0x00007fa0f2c0f000)
        libcudart.so.4 => not found

前三個庫表明存在路徑,而後兩個存在問題。解決後兩個庫的依賴:

  • 方法一:添加為解析庫的路徑到/etc/ld.so.conf.d/name-of-lib-x86_64.conf 和/或 /etc/ld.so.conf.d/name-of-lib-i686.conf,然後用sodu ldconfig再加載庫緩存
  • 方法二:添加庫庫和路徑到編譯/鏈接命令: -lname-of-lib -L/path/to/lib
  • 方法三:添加庫路徑到環境變量解決運行依賴問題:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/lib

運行程序

  • 設置路徑: export LD_LIBRARY_PATH=/opt/lib:$LD_LIBRARY_PATH
  • 運行:prog

主要名詞:

  • gcc -GNU的c編譯器
  • ld -GNU的鏈接器
  • ldd -列出庫的依賴項
  • ldconfig -配置動態鏈接器運行時綁定對象(即,更新緩存/etc/ld.so.cache)

庫路徑

為了讓可執行程序找到需要的庫,並在運行時鏈接,必須對系統進行一些配置,相關的配置方法有以下:

  1. 添加在動態鏈接時需要包含的庫目錄到/etc/ld.so.conf文件中,執行ldconfig(root權限)配置鏈接器在運行時的綁定對象。你可以使用-f file-name標識引用其他配置文件。
  2. 添加指定目錄到庫緩存: ldconfig -n /opt/lib。其中,/opt/lib是包含你的庫ctest.so的目錄,但這種方式不會對系統永久生效,重啟後會丟失。例如,當前目錄ldconfig -n .,鏈接使用-L.即可。
  3. 指定環境變量LD_LIBRARY_PATH,使其包含共享庫所在的目錄。export LD_LIBRARY_PATH=/OPT/LIB:$LD_LIBRARY_PATH,或者編輯~/.bashrc文件:
...
if [ -d /opt/lib ];
then
   LD_LIBRARY_PATH=/opt/lib:$LD_LIBRARY_PATH
fi
...
export LD_LIBRARY_PATH

庫信息

ar:列舉在archive庫中的所有對象文件
ar tf /usr/lib/x86_64-linux-gnu/libjpeg.a

nm:列舉符號,包括對象文件、archive庫和共享庫
nm file.o #列舉對象文件中包含的符號
nm /usr/lib/x86_64-linux-gnu/libjpeg.a #列舉包含在archive庫中的符號。
使用-D列巨額包含在對象文件或共享庫中的符號。

使用libdl動態加載和卸載共享庫

在執行時可以動態加載/卸載這些庫。
庫的包含文件ctest.h如下所示:使用extern "c"方便該庫可以用在c和c++裏。這個語句可以防止鏈接時因為C++的名字隔離而導致的為解析符號。

#ifndef CTEST_H
#define CTEST_H

#ifdef __cplusplus
extern "C" {
#endif

void ctest1(int *);
void ctest2(int *);

#ifdef __cplusplus
}
#endif

#endif

動態加載或卸載libctest.so庫(progdl.c):

#include <stdio.h>
#include <dlfcn.h>
#include "ctest.h"

int main(int argc, char **argv) 
{
   void *lib_handle;
   double (*fn)(int *);
   int x;
   char *error;

   lib_handle = dlopen("/opt/lib/libctest.so", RTLD_LAZY);
   if (!lib_handle) 
   {
      fprintf(stderr, "%s\n", dlerror());
      exit(1);
   }

   fn = dlsym(lib_handle, "ctest1");
   if ((error = dlerror()) != NULL)  
   {
      fprintf(stderr, "%s\n", error);
      exit(1);
   }

   (*fn)(&x);
   printf("Valx=%d\n",x);

   dlclose(lib_handle);
   return 0;
}

編譯命令:gcc -rdynamic -o progdl progdl.c -ldl

解釋:

  • dlopen("/opt/lib/libctest.so", RTLD_LAZY);打開名字為”libctest.so“的共享連接庫,第二個參數顯示綁定,在dlfcn.h文件中定義,如果失敗了返回NULL。
    選項: RTLD_LAZY:如果指定,linux並不關心未解析符號直到被引用;RTLD_NOW:所有未解析符號都會被解析在調用dlopen時;RT_GLOBAL,讓符號庫可見。
  • dlsym(lib_handle, "ctest1"); 換回加載的共享庫的函數地址;如果失敗返回值為NULL。註意:當使用c++函數時,首先使用nm找到mangles符號名字,或者使用extern "C"避免名字mangling. 例如,extern "C" void function-name();

C++類對象和動態加載

C++和名字修飾(name mangling)

當使用c++編譯上面的c例子,會發現c++函數名字被修飾而導致不可用,除非函數定義有extern "C"{}保護。
註意,以下兩者不等價:

//1:
extern "C" {
    int functionx();
}
//2:
extern "C" int functionx();

以下兩者是等價的:

//1:
extern "C" {
    extern int functionx();
}
//2:
extern "C" int functionx();

動態加載C++類:
動態庫加載程序可以使得編程者加載C函數。在C++中,我們想要加載類的成員函數,實際上,整個類可以在庫中,我們先要加載並訪問整個對象以及它的成員函數。通過傳遞”C“類工廠函數可以實現。
類的頭文件如下所示:

class Abc {

...
...

};

// Class factory "C" functions

typedef Abc* create_t;
typedef void destroy_t(Abc*);

對應的cpp文件如下:

Abc::Abc()
{
    ...
}

extern "C"
{
   // These two "C" functions manage the creation and destruction of the class Abc

   Abc* create()
   {
      return new Abc;
   }

   void destroy(Abc* p)
   {
      delete p;   // Can use a base class or derived class pointer here
   }
}

這個cpp文件是庫的源文件,C函數在庫中動態加載庫中實例化、銷毀一個類,Abc就是這個類。

包含Main可執行程序調用這個庫的方法如下:

// load the symbols
    create_t* create_abc = (create_t*) dlsym(lib_handle, "create");

...
...

    destroy_t* destroy_abc = (destroy_t*) dlsym(lib_handle, "destroy");

...
...

註意: C++類的new/delete應該都由可執行程序或者庫來提供,不應該分開。以便如果在一方出現了new/delete的重載不會導致意外情況。

和DLL的比較

windows下和linux/Unix共享對象(.so)對應的是.dll,通常windows下是.dll,有時也可能是.ocx。 在舊的16位系統上,動態鏈接庫也可能為.exe。不幸的是,windows下Dll的生成和Microsoft IDE緊密結合,沒有IDE基本沒有辦法自己生成。
windows c++對應的函數:

  • ::LoadLibrary() - dlopen()
  • ::GetProcAddress() -dlsym()
  • ::FreeLibrary() -dlclose()

跨平臺代碼片段

包含頭文件(.h/.hpp):

class Abc{
public:
   static Abc* Instance(); // Function declaration. Could also be used as a public class member function.

private:
   static Abc *mInstance;      // Singleton. Use this declaration in C++ class member variable declaration.
   ...
}

對應的cpp文件:

/// Singleton instantiation
Abc* Abc::mInstance = 0;   // Use this declaration for C++ class member variable
                           // (Defined outside of class definition in ".cpp" file)

// Return unique pointer to instance of Abc or create it if it does not exist.
// (Unique to both exe and dll)

static Abc* Abc::Instance() // Singleton
{
#ifdef WIN32
    // If pointer to instance of Abc exists (true) then return instance pointer else look for 
    // instance pointer in memory mapped pointer. If the instance pointer does not exist in
    // memory mapped pointer, return a newly created pointer to an instance of Abc.

    return mInstance ? 
       mInstance : (mInstance = (Abc*) MemoryMappedPointers::getPointer("Abc")) ? 
       mInstance : (mInstance = (Abc*) MemoryMappedPointers::createEntry("Abc",(void*)new Abc));
#else
    // If pointer to instance of Abc exists (true) then return instance pointer 
    // else return a newly created pointer to an instance of Abc.

    return mInstance ? mInstance : (mInstance = new Abc);
#endif
}

windows鏈接器將會鏈接入兩個對象實例,一個在exe,一個在可加載模塊內。通過內存映射指針可對兩者進行明確,以便exe和可加載庫指向同一個變量或者對象。而GNU 鏈接器不存在這個問題。
交叉平臺可加載庫的編程:

#ifndef USE_PRECOMPILED_HEADERS
#ifdef WIN32
#include <direct.h>
#include <windows.h>
#else
#include <sys/types.h>
#include <dlfcn.h>
#endif
#include <iostream>
#endif

    using namespace std;

#ifdef WIN32
    HINSTANCE lib_handle;
#else
    void *lib_handle;
#endif

    // Where retType is the pointer to a return type of the function
    // This return type can be int, float, double, etc or a struct or class.

    typedef retType* func_t;  

    // load the library -------------------------------------------------
#ifdef WIN32
    string nameOfLibToLoad("C:\opt\lib\libctest.dll");
    lib_handle = LoadLibrary(TEXT(nameOfLibToLoad.c_str()));
    if (!lib_handle) {
        cerr << "Cannot load library: " << TEXT(nameOfDllToLoad.c_str()) << endl;
    }
#else
    string nameOfLibToLoad("/opt/lib/libctest.so");
    lib_handle = dlopen(nameOfLibToLoad.c_str(), RTLD_LAZY);
    if (!lib_handle) {
        cerr << "Cannot load library: " << dlerror() << endl;
    }
#endif

...
...
...

    // load the symbols -------------------------------------------------
#ifdef WIN32
    func_t* fn_handle = (func_t*) GetProcAddress(lib_handle, "superfunctionx");
    if (!fn_handle) {
        cerr << "Cannot load symbol superfunctionx: " << GetLastError() << endl;
    }
#else
    // reset errors
    dlerror();

    // load the symbols (handle to function "superfunctionx")
    func_t* fn_handle= (func_t*) dlsym(lib_handle, "superfunctionx");
    const char* dlsym_error = dlerror();
    if (dlsym_error) {
        cerr << "Cannot load symbol superfunctionx: " << dlsym_error << endl;
    }
#endif

...
...
...

    // unload the library -----------------------------------------------

#ifdef WIN32
    FreeLibrary(lib_handle);
#else
    dlclose(lib_handle);
#endif

【ubuntu】linux鏈接庫