(C/C++ ) 如何在 Linux 上使用自行編譯的第三方函式庫

fcamel
7 min readAug 12, 2018

--

先前是由系統工具的角度說明 static 和 dynamic linker 的運作原理和如何使用它們:

這篇反過來,從使用情境出發,以使用 LevelDB 為例,說明每個步驟遇到的問題,和如何解決。

備註: 這篇是改寫 2017 用 Ubuntu 14.04 編譯 LevelDB 的記錄,現在編 LevelDB 的方式和產生檔案的位置有點不同,不過使用 LevelDB (或任何第三方函式庫) 會遇到的問題,是一樣的。

準備環境

抓好並編好 LevelDB 的 shared library,編譯方式見第三方函式庫附的說明。

編好後要找出 header 和 shared library 的位置。

$ ls include/  # header files
leveldb/
$ ls out-shared/libleveldb.so* # shared library
out-shared/libleveldb.so@ out-shared/libleveldb.so.1@ out-shared/libleveldb.so.1.20*

接下來在同樣目錄下寫 sample.cpp 使用 LevelDB。

下面的例子用 clang++ 編譯,這裡用到的參數和 g++ 一樣。

問題一:找不到 header (compilation error)

$ clang++ sample.cpp
sample.cpp:5:10: fatal error: 'leveldb/db.h' file not found
#include "leveldb/db.h"
^
1 error generated.

解法:用 -I 指定 header 位置,像是 clang++ sample.cpp -I include/

之後若希望 header 位置讓大家都方便使用,可以複製 LevelDB 的 include/usr/local/include/leveldb/include,指令改為 clang++ sample.cpp -I /usr/local/include/leveldb/include

問題二:找不到 shared library (linking error)

$ clang++ sample.cpp -I include/
/tmp/sample-2e7dd8.o: In function `main':
sample.cpp:(.text+0x1e): undefined reference to `leveldb::Options::Options()'
sample.cpp:(.text+0x6f): undefined reference to `leveldb::DB::Open(leveldb::Options const&, std::string const&, leveldb::DB**)'
sample.cpp:(.text+0x10c): undefined reference to `leveldb::Status::ToString() const'
sample.cpp:(.text+0x7d0): undefined reference to `leveldb::Status::ToString() const'
clang: error: linker command failed with exit code 1 (use -v to see invocation)

要求 linker 連結 libleveldb.so (linker 的參數由 clang++ / g++ 轉傳):

$ clang++ sample.cpp -I include/ -l leveldb
/usr/bin/ld: cannot find -lleveldb
clang: error: linker command failed with exit code 1 (use -v to see invocation)

但 compiler 說找不到要連結的 library

補上 libleveldb 的位置:

$ clang++ sample.cpp -I include/ -l leveldb -L out-shared/

問題三:執行時找不到 shared library

$ ./a.out
./a.out: error while loading shared libraries: libleveldb.so.1: cannot open shared object file: No such file or directory

編出 executable file 或 shared library 表示 static linker 成功,但執行時會用到 dynamic linker 載入函式庫。這錯誤訊息是dynamic linker 回報的。

用 ldd 可以檢查 shared library 的路徑是否正確:

$ ldd a.out | grep leveldb
libleveldb.so.1 => not found

幾種解法:

1. 用 LD_LIBRARY_PATH 指定位置

$ LD_LIBRARY_PATH=`pwd`/out-shared ./a.out

通常不建議這麼作,其它作法比較「乾淨」。

2. 將 library path 寫到 executable

$ clang++ sample.cpp -I include/ -l leveldb -L out-shared/ -Wl,-rpath,`pwd`/out-shared 
$ objdump -p a.out | grep PATH # 確認有記錄
RPATH /home/fcamel/dev/study/leveldb/out-shared
$ ldd a.out | grep leveldb # 也可用 ldd 確認
libleveldb.so.1 => /home/fcamel/dev/study/leveldb/out-shared/libleveldb.so.1 (0x00007fc1f091e000)

這裡我用絕對路徑減少潛在的問題。若 libleveldb 和 a.out 一定會放在同樣位置,也可以用 -Wl,rpath,’$ORIGIN’/lib

3. 搬到系統函式庫

$ ldd a.out  | grep leveldb
libleveldb.so.1 => not found
$ sudo su
$ cp out-shared/libleveldb.so* /usr/lib
$ ldd a.out | grep leveldb
libleveldb.so.1 => /usr/lib/libleveldb.so.1 (0x00007f1717026000)

但這樣和系統內建的混在一起,不好維護。改放到 /usr/local/lib/leveldb/ 下:

$ mkdir /usr/local/lib/leveldb
$ cp --preserve=links out-shared/libleveldb.so* /usr/local/lib/leveldb/
$ echo "/usr/local/lib/leveldb" > /etc/ld.so.conf.d/leveldb.conf
$ ldconfig # Update ldconfig's cache
$ ldd a.out | grep leveldb
libleveldb.so.1 => /usr/local/lib/leveldb/libleveldb.so.1 (0x00007f0314b32000)

ldconfig 會讀 /etc/ld.so.conf。我在 Ubuntu 14.04 看到的設定如下:

$ cat /etc/ld.so.conf
include /etc/ld.so.conf.d/*.conf

所以在 /etc/ld.so.conf.d/ 建新檔案寫入 /usr/local/lib/leveldb,然後更新 ldconfig cache 即可。

用 checkinstall 安裝

在 Ubuntu 上用 checkinstall 安裝更好,可以打包成 debian package,方便用系統工具列出安裝的內容和移除。

相關資料

--

--