【踩坑】链接第三方动态库

前言

每一个今天你绕过去不填的坑,都会在未来等着你。
—哲·士沃硕德

正文

一个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

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