SLAM算法与工程实践系列文章
下面是SLAM算法与工程实践系列文章的总链接,本人发表这个系列的文章链接均收录于此
SLAM算法与工程实践系列文章链接
下面是专栏地址:
SLAM算法与工程实践系列专栏
文章目录
前言
这个系列的文章是分享SLAM相关技术算法的学习和工程实践
SLAM算法与工程实践——CMake使用(3)
制作动态库或静态库
有些时候我们编写的源代码并不需要将他们编译生成可执行程序,而是生成一些静态库或动态库提供给第三方使用,下面来讲解在 cmake 中生成这两类库文件的方法。
制作静态库
在 cmake 中,如果要制作静态库,需要使用的命令如下:
add_library(库名称 STATIC 源文件1 [源文件2] ...)
ADD_LIBRARY(libname [SHARED|STATIC|MODULE]
[EXCLUDE_FROM_ALL]
source1 source2 ... sourceN)
在 Linux 中,静态库名字分为三部分:lib+ 库名字 +.a
,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。
类型有三种:
SHARED
,动态库STATIC
,静态库,如果不指定,add_library
默认生成是静态库MODULE
,在使用 dyld 的系统有效,如果不支持 dyld,则被当作 SHARED 对待。
EXCLUDE_FROM_ALL
参数的意思是这个库不会被默认构建,除非有其他的组件依赖或者手
工构建。
在 Windows 中虽然库名和 Linux 格式不同,但也只需指定出名字即可。
下面有一个目录,需要将 src 目录中的源文件编译成静态库,然后再使用:
.
├── build
├── CMakeLists.txt
├── include # 头文件目录
│ └── head.h
├── main.cpp # 用于测试的源文件
└── src # 源文件目录
├── add.cpp
├── div.cpp
├── mult.cpp
└── sub.cpp
根据上面的目录结构,可以这样编写 CMakeLists.txt 文件:
cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
add_library(calc STATIC ${SRC_LIST})
这样最终就会生成对应的静态库文件 libcalc.a
。
制作动态库
在 cmake 中,如果要制作动态库,需要使用的命令如下:
add_library(库名称 SHARED 源文件1 [源文件2] ...)
在 Linux 中,动态库名字分为三部分:lib+ 库名字 +.so,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。
在 Windows 中虽然库名和 Linux 格式不同,但也只需指定出名字即可。
根据上面的目录结构,可以这样编写 CMakeLists.txt 文件:
cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
add_library(calc SHARED ${SRC_LIST})
这样最终就会生成对应的动态库文件 libcalc.so
。
指定输出的路径
方式 1 - 适用于动态库
对于生成的库文件来说和可执行程序一样都可以指定输出路径。由于在Linux下生成的动态库默认是有执行权限的,所以可以按照生成可执行程序的方式去指定它生成的目录:
cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
# 设置动态库生成路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
add_library(calc SHARED ${SRC_LIST})
对于这种方式来说,其实就是通过 set 命令给 EXECUTABLE_OUTPUT_PATH
宏设置了一个路径,这个路径就是可执行文件生成的路径。
方式 2 - 都适用
由于在 Linux 下生成的静态库默认不具有可执行权限,所以在指定静态库生成的路径的时候就不能使用 EXECUTABLE_OUTPUT_PATH 宏了,而应该使用 LIBRARY_OUTPUT_PATH,这个宏对应静态库文件和动态库文件都适用。
cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
# 设置动态库/静态库生成路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
# 生成动态库
#add_library(calc SHARED ${SRC_LIST})
# 生成静态库
add_library(calc STATIC ${SRC_LIST})
同时生成动态库和静态库
下面我们用这个指令再来添加静态库:
ADD_LIBRARY(calc STATIC ${SRC_LIST})
然后再在 build 目录进行外部编译,我们会发现,静态库根本没有被构建,仍然只生成了一个动态库。因为 calc 作为一个 target 是不能重名的,所以,静态库构建指令无效。
这种结果显示不是我们想要的,我们需要的是名字相同的静态库和动态库,因为 target 名
称是唯一的,所以,我们肯定不能通过 ADD_LIBRARY
指令来实现了。这时候我们需要用到另外一个指令:
# SET_TARGET_PROPERTIES,其基本语法是:
SET_TARGET_PROPERTIES(target1 target2 ...
PROPERTIES prop1 value1
prop2 value2 ...)
这条指令可以用来设置输出的名称,对于动态库,还可以用来指定动态库版本和 API 版本。
在本例中,我们需要作的是向 lib/CMakeLists.txt
中添加一条:
SET_TARGET_PROPERTIES(calc_static PROPERTIES OUTPUT_NAME "calc")
这样,我们就可以同时得到 libcalc.so
和libcalc.a
两个库了。
与他对应的指令是:
GET_TARGET_PROPERTY(VAR target property)
具体用法如下例,我们向 lib/CMakeListst.txt 中添加:
GET_TARGET_PROPERTY(OUTPUT_VALUE calc_static OUTPUT_NAME)
MESSAGE(STATUS “This is the calc_static OUTPUT_NAME:”${OUTPUT_VALUE})
如果没有这个属性定义,则返回 NOTFOUND.
让我们来检查一下最终的构建结果,我们发现,libhello.a 已经构建完成,位于build/lib
目录中,但是 libcalc.so
却消失了。这个问题的原因是:cmake 在构建一个新的 target 时,会尝试清理掉其他使用这个名字的库,因为,在构建 libcalc.a
时,就会清理掉 libhello.so
.
为了回避这个问题,比如再次使用 SET_TARGET_PROPERTIES
定义
CLEAN_DIRECT_OUTPUT
属性。
向 lib/CMakeLists.txt
中添加:
SET_TARGET_PROPERTIES(calc PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(calc_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
这时候,我们再次进行构建,会发现 build/lib 目录中同时生成了 libcalc.so 和 libcalc.a
动态库版本号
按照规则,动态库是应该包含一个版本号的,我们可以看一下系统的动态库,一般情况是
libhello.so.1.2
libhello.so ->libhello.so.1
libhello.so.1->libhello.so.1.2
为了实现动态库版本号,我们仍然需要使用SET_TARGET_PROPERTIES
指令。
具体使用方法如下:
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
VERSION
指代动态库版本,SOVERSION
指代 API 版本。
将上述指令加入 lib/CMakeLists.txt 中,重新构建看看结果。
在 build/lib 目录会生成:
libhello.so.1.2
libhello.so.1->libhello.so.1.2
libhello.so ->libhello.so.1
总结-生成动静态库
生成动态库代码如下
# 先编译动态库或者静态库
cmake_minimum_required(VERSION 3.0)
project(test)
# 指定头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)
# 寻找源文件
# aux_source_directory(${PROJECT_SOURCE_DIR} SRC)
file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
# 设置单独动态库生成路径
set(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/lib_shared)
# 设置单独单独静态库生成路径
# set(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/lib_static)
# 单独生成动态库
add_library(calc SHARED ${SRC})
# 单独生成静态库
# add_library(calc STATIC ${SRC})
若要同时生成动态库和静态库
cmake_minimum_required(VERSION 3.0)
project(CALCLIB)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
include_directories(${PROJECT_SOURCE_DIR}/include)
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
add_library(calc_shared SHARED ${SRC_LIST})
SET_TARGET_PROPERTIES(calc_shared PROPERTIES CLEAN_DIRECT_OUTPUT 1)
add_library(calc_static STATIC ${SRC_LIST})
SET_TARGET_PROPERTIES(calc_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
包含库文件
在编写程序的过程中,可能会用到一些系统提供的动态库或者自己制作出的动态库或者静态库文件,cmake 中也为我们提供了相关的加载动态库的命令。
链接静态库
src
├── add.cpp
├── div.cpp
├── main.cpp
├── mult.cpp
└── sub.cpp
现在我们把上面 src 目录中的 add.cpp、div.cpp、mult.cpp、sub.cpp 编译成一个静态库文件 libcalc.a
。
测试目录结构如下:
$ tree
.
├── build
├── CMakeLists.txt
├── include
│ └── head.h
├── lib
│ └── libcalc.a # 制作出的静态库的名字
└── src
└── main.cpp
4 directories, 4 files
在 cmake 中,链接静态库的命令如下:
link_libraries(<static lib> [<static lib>...])
-
参数 1:指定出要链接的静态库的名字
- 可以是全名 libxxx.a
- 也可以是掐头(lib)去尾(.a)之后的名字 xxx
-
参数 2-N:要链接的其它静态库的名字
如果该静态库不是系统提供的(自己制作或者使用第三方提供的静态库)可能出现静态库找不到的情况,此时可以将静态库的路径也指定出来:
link_directories(<lib path> [<lib path>]...)
这样,修改之后的 CMakeLists.txt 文件内容如下:
cmake_minimum_required(VERSION 3.0)
project(CALC)
# 搜索指定目录下源文件
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
# 包含头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)
# 包含静态库路径
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 链接静态库
link_libraries(calc)
add_executable(app ${SRC_LIST})
添加了第 8 行的代码,就可以根据参数指定的路径找到这个静态库了。
链接动态库
在程序编写过程中,除了在项目中引入静态库,很多时候也会使用一些标准的或者第三方提供的一些动态库
在 cmake 中链接动态库的命令如下:
target_link_libraries(
<target>
<PRIVATE|PUBLIC|INTERFACE> <item>...
[<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
-
target
:指定要加载动态库的文件的名字- 该文件可能是一个源文件
- 该文件可能是一个动态库文件
- 该文件可能是一个可执行文件
-
PRIVATE|PUBLIC|INTERFACE
:动态库的访问权限,默认为 PUBLIC-
如果各个动态库之间没有依赖关系,无需做任何设置,三者没有没有区别,一般无需指定,使用默认的 PUBLIC 即可。
-
动态库的链接具有传递性,如果动态库 A 链接了动态库 B、C,动态库 D 链接了动态库 A,此时动态库 D 相当于也链接了动态库 B、C,并可以使用动态库 B、C 中定义的方法。
target_link_libraries(A B C) target_link_libraries(D A)
PUBLIC
:在 public 后面的库会被 Link 到前面的 target 中,并且里面的符号也会被导出,提供给第三方使用。PRIVATE
:在 private 后面的库仅被 link 到前面的 target 中,并且终结掉,第三方不能感知你调了啥库INTERFACE
:在 interface 后面引入的库不会被链接到前面的 target 中,只会导出符号。
-
链接系统动态库
动态库的链接和静态库是完全不同的:
- 静态库会在生成可执行程序的链接阶段被打包到可执行程序中,所以可执行程序启动,静态库就被加载到内存中了。
- 动态库在生成可执行程序的链接阶段不会被打包到可执行程序中,当可执行程序被启动并且调用了动态库中的函数的时候,动态库才会被加载到内存
因此,在 cmake 中指定要链接的动态库的时候,应该将命令写到生成了可执行文件之后:
cmake_minimum_required(VERSION 3.0)
project(TEST)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
# 添加并指定最终生成的可执行程序名
add_executable(app ${SRC_LIST})
# 指定可执行程序要链接的动态库名字
# 注意,动态库的链接要写到最后,因为程序执行时只有需要动态库的时候,动态库才会被加载到内存中
target_link_libraries(app pthread)
在 target_link_libraries(app pthread)
中:
- app: 对应的是最终生成的可执行程序的名字
- pthread:这是可执行程序要加载的动态库,这个库是系统提供的线程库,全名为 libpthread.so,在指定的时候一般会掐头(lib)去尾(.so)。
链接第三方动态库
现在,自己生成了一个动态库,对应的目录结构如下:
$ tree
.
├── build
├── CMakeLists.txt
├── include
│ └── head.h # 动态库对应的头文件
├── lib
│ └── libcalc.so # 自己制作的动态库文件
└── main.cpp # 测试用的源文件
3 directories, 4 files
假设在测试文件 main.cpp 中既使用了自己制作的动态库 libcalc.so 又使用了系统提供的线程库,此时 CMakeLists.txt 文件可以这样写:
cmake_minimum_required(VERSION 3.0)
project(TEST)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
include_directories(${PROJECT_SOURCE_DIR}/include)
add_executable(app ${SRC_LIST})
target_link_libraries(app pthread calc)
在第六行中,pthread、calc 都是可执行程序 app 要链接的动态库的名字。当可执行程序 app 生成之后并执行该文件,会提示有如下错误信息:
$ ./app
./app: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory
这是因为可执行程序启动之后,去加载 calc 这个动态库,但是不知道这个动态库被放到了什么位置,见解决动态库无法加载的问题,所以就加载失败了,在 CMake 中可以在生成可执行程序之前,通过命令指定出要链接的动态库的位置,指定静态库位置使用的也是这个命令:
link_directories(path)
所以修改之后的 CMakeLists.txt 文件应该是这样的:
cmake_minimum_required(VERSION 3.0)
project(TEST)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
# 指定源文件或者动态库对应的头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)
# 指定要链接的动态库的路径
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 添加并生成一个可执行程序
add_executable(app ${SRC_LIST})
# 指定要链接的动态库
target_link_libraries(app pthread calc)
通过 link_directories 指定了动态库的路径之后,在执行生成的可执行程序的时候,就不会出现找不到动态库的问题了。
温馨提示:使用
target_link_libraries
命令就可以链接动态库,也可以链接静态库文件。
总结-链接库
链接库实际运行代码如下
cmake_minimum_required(VERSION 3.0)
project(test)
# 指定头文件
include_directories(${PROJECT_SOURCE_DIR}/include)
# aux_source_directory(${PROJECT_SOURCE_DIR} SRC)
# 注意这里要寻找 main.c文件,与之前不一样,当然,不去寻找 main.c文件也可以,cmake会自动去当前目录寻找
file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.c)
# 链接静态库
link_directories(${PROJECT_SOURCE_DIR}/lib_static)
link_libraries(calc)
# 指定输出路径,不设置的话默认输出到build文件夹中
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR})
add_executable(app ${SRC})
# 链接动态库
link_directories(${PROJECT_SOURCE_DIR}/lib_shared)
# 指定输出路径,不设置的话默认输出到build文件夹中
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR})
add_executable(app ${SRC})
# 注意,动态库的链接要写到最后,因为程序执行时只有需要动态库的时候,动态库才会被加载到内存中
target_link_libraries(app calc)