【踩坑】鏈接第三方動態庫

前言

每一個今天你繞過去不填的坑,都會在未來等着你。
—哲·士沃碩德

正文

一個C/C++程序從源碼到可執行文件都需要經過 預處理-編譯-彙編-鏈接 這幾個過程,當然現在只需要gcc x.c就可以了,而不需要我們去執行具體的cpp等程序了,非常的方便。
回到今天的case上來,當我們需要編寫一個依賴第三方庫的程序時,該如何gcc x.c呢?

以Redis的C客戶端hiredis爲例,讓我們看一下它的Makefile是怎控制編譯鏈接的。

INSTALL?= cp -a

$(PKGCONFNAME): hiredis.h
        @echo "Generating $@ for pkgconfig..."
        @echo prefix=$(PREFIX) > $@
        @echo exec_prefix=\$${prefix} >> $@
        @echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@
        @echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@
        @echo >> $@
        @echo Name: hiredis >> $@
        @echo Description: Minimalistic C client library for Redis. >> $@
        @echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@
        @echo Libs: -L\$${libdir} -lhiredis >> $@
        @echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@

install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
        mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH)
        $(INSTALL) hiredis.h async.h read.h sds.h adapters $(INSTALL_INCLUDE_PATH)
        $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
        cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME)
        $(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH)
        mkdir -p $(INSTALL_PKGCONF_PATH)
        $(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH)

可以看出,當我們執行make install時,將頭文件複製到了指定的include目錄,將so文件放到的lib下,並且生成了軟鏈接,更新了pkgconfig。

這是非常標準的做法了,因此現在我們的libhireids其實和libstdc++差不多了。都放在了同一位置。
但如果我們不能執行make install,或者說只編譯出了so文件,要怎麼手動鏈接使用這個庫呢?

首先需要頭文件,這個很好解決,在編譯時通過指定-I參數,設置頭文件尋找的路徑。

比較麻煩的是鏈接動態庫。在編譯時通過指定-L參數,設置動態庫尋找的路徑。

但這樣編譯不報錯誤就完了嗎?就可以成功執行了嗎?
直接執行,往往會報這樣錯誤

./a.out: error while loading shared libraries: libhiredis.so.0.13: cannot open shared object file: No such file or directory

ldd命令可以查看可執行文件依賴了哪些庫
libhiredis.so.0.13 => not found
可以看到,似乎並沒有鏈接成功啊

這是爲啥呢,如何才能鏈接成功呢?

之前在學校遇到這種情況,我會將路徑添加到/etc/ldconfig.so.conf ,再執行ldconfig刷新配置,就可以成功鏈接了。雖然不懂爲啥,但一直可以使用,結果這次在服務器上並沒有sudo權限,所以也不能執行這樣的操作了。

先說解決方案,在編譯時添加參數-Wl,-rpath=/xx/hiredis/,就可以成功了。

再說爲啥,這個參數-Wl,-rpath= 是gcc傳遞給鏈接器的,我對裝載鏈接也不是很懂,但現在的理解就是,-L參數,是在編譯時告訴鏈接器動態鏈接庫的路徑,-Wl,-rpath=則告訴了鏈接器程序運行時,動態鏈接庫的路徑,當然,這兩個路徑應該是一樣的。

我們之前修改ldconfig的配置文件,實際上是指定了全局的動態鏈接庫搜索路徑,這應該保證了編譯和運行時我們都不需要再指定路徑了(存疑,沒有測試)。
-Wl,-rpath=-L都是指定了本次的路徑。

tips

那麼makefile中,爲什麼還要添加軟鏈接呢?
我們可以看到,動態庫都是有版本號的,如上面的libhiredis.so.0.13,但是我們在鏈接庫時,不會告訴編譯器,我要鏈接libhiredis.so.0.13,這樣以後動態庫升級版本了,所有的makefile都要跟着更新版本,非常的麻煩。
而建一個軟鏈,名字統一爲libhiredis.so,makefile就鏈接這個文件名,而之後版本升級,並不需要改makefile,只修改鏈接關係就可以了。
嗯,這其實就相當於做了一層封裝~

參考閱讀

https://stackoverflow.com/questions/8482152/whats-the-difference-between-rpath-and-l
https://stackoverflow.com/questions/6562403/i-dont-understand-wl-rpath-wl

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章