现有如下问题:
我们在第三方动态库(比如 boost 库)的基础上,开发了自己的动态库供公司内部项目使用。在使用自己的这个动态库的时候,该如何进行编译呢?即,依赖链条是这样的情况下:
程序–(依赖)–>libA.so–(依赖)–>libB.so
该如何进行编译。
为了研究这个问题,我们建立一个目录结构,写几个简单程序来模拟一下。以下内容将从构建动态库开始,一步步展示如何达成我们的目标。
建立试验目录结构、程序
目录结构
learn
|
|--main.cpp
|
|--upperLayer
| |--uLayer.h
| |--uLayer.cpp
|
|--bottomLayer
|--bLayer.h
|--bLayer.cpp
我们将在这个目录下,生成 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_H
int addOne(int n);
#endif
bottomLayer/bLayer.cpp
#include "bLayer.h"
int addOne(int n)
{
return n + 1;
}
upperLayer/uLayer.h
#ifndef U_LAYER_H
#define U_LAYER_H
int needAddOne(int n);
#endif
upperLayer/uLayer.cpp
#include "uLayer.h"
#include "../bottomLayer/bLayer.h"
int needAddOne(int n)
{
return addOne(n);
}
main.cpp
#include "upperLayer/uLayer.h"
#include <iostream>
using namespace std;
int main()
{
cout << needAddOne(4) << endl;
return 0;
}
这几个程序都非常简单,无需多言。
开始逐层编译
现在,我们从最底层开始逐步往上层编译。
libblayer.so
bottomLayer$ g++ -fPIC -shared bLayer.cpp -o libblayer.so
libblayer.so 比较简单,直接生成就好了。-fPIC 是要生成位置无关的代码,-shared 是要生成动态库。
libulayer.so
upperLayer$ g++ -fPIC -shared uLayer.cpp -o libulayer.so -L ../bottomLayer/ -lblayer
编译 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
什么鬼!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/
-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)
main 可执行文件
现在万事具备,只欠东风了,我们来编译 main 可执行文件:
g++ main.cpp -o main -L upperLayer/ -lulayer -Wl,-rpath upperLayer/
可以注意到,main 的编译参数只关心自己直接用到的 libulayer.so,而无需关心更底层的 libblayer.so 。运行一下看看:
learn$ ./main
5
得到了正确结果。
使用 CMake
上文通过实例简单介绍了 Linux 动态库编译与多重依赖的解决方法,编译命令直接使用的是 gcc。下面,我们来看看怎么用 CMake 来完成这项工作。
和上面的步骤类似,我们先生成 libblayer.so,然后再生成 libulayer.so,最后生成 main 可执行文件。我们的目标是提供给用户这样一个目录结构的程序包:
|
|--main
|
|--libs
|--libulayer.so
|--libblayer.so
可执行程序 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})
然后新建 build 文件夹,进入 build,进行编译
mkdir build
cd build
cmake ..
make
然后我们就得到了 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 自行加入任何 RPATH
SET(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})
编译步骤和上面一样,我们将得到一个 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]
...
从上面可以看到,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)
至此,生成的多重依赖的可执行文件 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