Linux 链接库编译与多重依赖

来源:互联网 发布:淘宝 冰毒 编辑:程序博客网 时间:2024/06/16 09:30

现有如下问题:

我们在第三方动态库(比如 boost 库)的基础上,开发了自己的动态库供公司内部项目使用。在使用自己的这个动态库的时候,该如何进行编译呢?即,依赖链条是这样的情况下:

程序–(依赖)–>libA.so–(依赖)–>libB.so

该如何进行编译。

为了研究这个问题,我们建立一个目录结构,写几个简单程序来模拟一下。以下内容将从构建动态库开始,一步步展示如何达成我们的目标。

建立试验目录结构、程序

目录结构

learn||--main.cpp||--upperLayer|    |--uLayer.h|    |--uLayer.cpp||--bottomLayer     |--bLayer.h     |--bLayer.cpp
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

我们将在这个目录下,生成 libblayer.so, libulayer.so, main(可执行文件),其依赖链条是:

main–>libulayer.so–>libblayer.so

特别的,对于 main 的编译来说,其编译参数无需指定 libblayer.so,它只需关心自己直接依赖的 libulayer.so 即可(当然,由于 libulayer.so 依赖 libblayer.so,系统中必须安装 libblayer.so)。

程序

bottomLayer/bLayer.h

#ifndef B_LAYER_H#define B_LAYER_Hint addOne(int n);#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

bottomLayer/bLayer.cpp

#include "bLayer.h"int addOne(int n){    return n + 1;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

upperLayer/uLayer.h

#ifndef U_LAYER_H#define U_LAYER_Hint needAddOne(int n);#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

upperLayer/uLayer.cpp

#include "uLayer.h"#include "../bottomLayer/bLayer.h"int needAddOne(int n){    return addOne(n);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

main.cpp

#include "upperLayer/uLayer.h"#include <iostream>using namespace std;int main(){    cout << needAddOne(4) << endl;    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这几个程序都非常简单,无需多言。

开始逐层编译

现在,我们从最底层开始逐步往上层编译。

libblayer.so

bottomLayer$ g++ -fPIC -shared bLayer.cpp -o libblayer.so
  • 1
  • 1

libblayer.so 比较简单,直接生成就好了。-fPIC 是要生成位置无关的代码,-shared 是要生成动态库。

libulayer.so

upperLayer$ g++ -fPIC -shared uLayer.cpp -o libulayer.so -L ../bottomLayer/ -lblayer
  • 1
  • 1

编译 libulayer.so 的命令前半部分和 libblayer.so 是一样的,但是由于它需要依赖于 libblayer.so,所以加了 -L 和 -l 参数,让它去 ../bottomLayer/ 找 libblayer.so 。要注意的是, -L 和 -l 不加可能也可以编译通过,但是因为找不到所依赖的文件,在最后运行可执行文件的时候,一定会出错,所以这里必须加上这两个参数。

现在我们来看看生成的 libulayer.so 是怎么样的依赖关系:

upperLayer$ ldd libulayer.so    linux-vdso.so.1 =>  (0x00007fffb2f6f000)    libblayer.so => not found
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

什么鬼!libblayer.so 找不到?!不是指定了 -L 和 -l 两个参数了么?不要慌。-L 和 -l 指定的是编译时要依赖的库文件和路径,但是动态库是在运行时才加载的,系统会到默认的路径(比如 /usr/lib)下去查找动态库,但是我们的 libblayer.so 并不在那些目录中,因此 not found 。

由于我们只是在试验,并不打算真的安装这些库,所以我们可以编译的时候把路径硬编码,告诉程序去哪里找 libblayer.so 。因此生成 libulayer.so 的真正命令是:

upperLayer$ g++ -fPIC -shared uLayer.cpp -o libulayer.so -L ../bottomLayer/ -lblayer -Wl,-rpath /home/neko/coding/learn/bottomLayer/
  • 1
  • 1

-Wl,-rpath 是告诉程序,在运行的时候如果找不到动态库,可以去 /home/neko/coding/learn/bottomLayer/ 看看(这个是我本机的路径,应设为绝对路径)。现在我们再来 ldd 一下:

upperLayer$ ldd libulayer.so     linux-vdso.so.1 =>  (0x00007ffd85ecb000)    libblayer.so => /home/neko/coding/learn/bottomLayer/libblayer.so (0x00007ffafef75000)
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

main 可执行文件

现在万事具备,只欠东风了,我们来编译 main 可执行文件:

g++ main.cpp -o main -L upperLayer/ -lulayer -Wl,-rpath upperLayer/
  • 1
  • 1

可以注意到,main 的编译参数只关心自己直接用到的 libulayer.so,而无需关心更底层的 libblayer.so 。运行一下看看:

learn$ ./main 5
  • 1
  • 2
  • 1
  • 2

得到了正确结果。

使用 CMake

上文通过实例简单介绍了 Linux 动态库编译与多重依赖的解决方法,编译命令直接使用的是 gcc。下面,我们来看看怎么用 CMake 来完成这项工作。

和上面的步骤类似,我们先生成 libblayer.so,然后再生成 libulayer.so,最后生成 main 可执行文件。我们的目标是提供给用户这样一个目录结构的程序包:

||--main||--libs     |--libulayer.so     |--libblayer.so
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

可执行程序 main 调用 libulayer.so,libulayer.so 根据需要调用 libblayer.so。这里我们无需把共享库安装到系统目录里,要让用户直接可以把 main 给运行起来。

libblayer.so

在 bottomLayer 目录下,新建文件 CMakeLists.txt 。输入如下内容:

CMAKE_MINIMUM_REQUIRED(VERSION 2.8)SET(PROJECT_NAME "blayer")PROJECT(${PROJECT_NAME} CXX)SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR})AUX_SOURCE_DIRECTORY(./ SRC_LIST)ADD_LIBRARY(${PROJECT_NAME} SHARED ${SRC_LIST})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

然后新建 build 文件夹,进入 build,进行编译

mkdir buildcd buildcmake ..make
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

然后我们就得到了 libblayer.so,这个过程很简单很普通,不多讲。

libulayer.so

libulayer.so 需要依赖于 libblayer.so,CMakeLists.txt 如下:

CMAKE_MINIMUM_REQUIRED(VERSION 2.8)SET(PROJECT_NAME "ulayer")PROJECT(${PROJECT_NAME} CXX)SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR})# 设置 libblayer.so 的路径LINK_DIRECTORIES("${CMAKE_SOURCE_DIR}/../bottomLayer") # 不允许 CMake 自行加入任何 RPATHSET(CMAKE_SKIP_BUILD_RPATH TRUE) # 生成的 libulayer.so 的 RPATH 是 "./" 和 "./libs"SET(CMAKE_SHARED_LINKER_FLAGS "-Wl,-rpath,./:./libs")AUX_SOURCE_DIRECTORY(./ SRC_LIST)ADD_LIBRARY(${PROJECT_NAME} SHARED ${SRC_LIST})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

编译步骤和上面一样,我们将得到一个 libulayer.so。通过 readelf 命令,我们可以来检查 rpath 是否已经设置正确:

$ readelf -d libulayer.so Dynamic section at offset 0xdb8 contains 30 entries:  Tag        Type                         Name/Value 0x0000000000000001 (NEEDED)             Shared library: [libblayer.so] 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6] 0x0000000000000001 (NEEDED)             Shared library: [libm.so.6] 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1] 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6] 0x000000000000000e (SONAME)             Library soname: [libulayer.so] 0x000000000000000f (RPATH)              Library rpath: [./:./libs]...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

从上面可以看到,libulayer.so 的 rpath 已经正确设置,而且它依赖于 libblayer.so。下面我们将开始最终的工作,生成 main 可执行文件。

main 可执行文件

在有了 libblayer.so 和 libulayer.so 以后,我们新建一个 libs 文件夹,把它们放进来,目录结构上文已述。

CMakeLists.txt 如下:

CMAKE_MINIMUM_REQUIRED(VERSION 2.8)SET(PROJECT_NAME "main")PROJECT(${PROJECT_NAME} CXX)# 注意这里变成 RUNTIME 了SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR})LINK_DIRECTORIES("${CMAKE_SOURCE_DIR}/libs")# 下面这句不能写,写了就编译不过了。但是不写的话,rpath 就会被 CMake 设置,加上了一个路径# 我水平有限,不知道怎么解决或者解释,暂且这样吧#SET(CMAKE_SKIP_BUILD_RPATH TRUE)# 注意这里变成 EXE 了SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,./:./libs")AUX_SOURCE_DIRECTORY(./ SRC_LIST)ADD_EXECUTABLE(${PROJECT_NAME} ${SRC_LIST})TARGET_LINK_LIBRARIES(${PROJECT_NAME} ulayer)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

至此,生成的多重依赖的可执行文件 main,可以正常的运行了。把 main,libs/libblayer.so, libs/libulayer.so 保持目录结构地移动到其它地方去,也都可以正常运行。目标达成。

本文链接:http://blog.csdn.net/shutdown_r_now/article/details/51991076 
参考文档:http://blog.csdn.net/nodeathphoenix/article/details/9982209