一个完整的 C++ 语言项目可能包含多个 .cpp 源文件,项目的运行需要经过“编译”和“链接”两个过程:
我们在开发过程中也经常会面临库文件和目标文件的概念,因此在做工程文件时,经常需要了解什么是库文件?什么是目标文件?什么是依赖项?
(2)动态库
动态库,windows下叫DLL,linux下叫共享库以.so结尾. (so ==share object) 在程序的链接时候并不像静态库那样在拷贝使用函数的代码,而只是作些标记。然后在程序开始启动运行的时候,动态地加载所需模块。所以,应用程序在运行的时候仍然需要共享库的支持。 共享库链接出来的文件比静态库要小得多。动态库的好处是,不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。
我们明确了上述的具体定义后,可以介绍一个工程文件从源文件到可执行文件的编译流程;具体编译流程如下:
① 预处理过程
预处理相当于根据预处理指令组装新的C/C++程序。经过预处理,会产生一个没有宏定义,没有条件编译指令,没有特殊符号的输出文件,这个文件的含义同原本的文件无异,只是内容上有所不同。
读取C/C++源程序,对其中的伪指令(以#开头的指令)进行处理,内容如下:
② 编译过程
将预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后,产生相应的汇编代码文件。
③ 汇编过程
将编译完的汇编代码文件翻译成机器指令,并生成可重定位目标程序的.o文件,该文件为二进制文件,字节编码是机器指令。
汇编器是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。所以汇编器的汇编过程相对于编译器来讲比较简单,它没有复杂的语法,也没有语义,也不需要做指令优化,只是根据汇编指令和机器指令的对照表一一翻译即可。
④ 链接(build)过程
通过链接器将一个个目标文件(或许还会有库文件)链接在一起生成一个完整的可执行程序。由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。
例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。
链接程序的主要工作就是将有关的目标文件彼此相连接,也就是将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。
cmake 是一个跨平台、开源的构建系统。它是一个集软件构建、测试、打包于一身的软件。它使用与平台和编译器独立的配置文件来对软件编译过程进行控制。
要了解CMakelist.txt文件,首先我们先了解一下Makefile。Makefile 可以简单的认为是一个工程文件的编译规则,描述了整个工程的编译和链接等规则。其中包含了那些文件需要编译,那些文件不需要编译,那些文件需要先编译,那些文件需要后编译,那些文件需要重建等等。编译整个工程需要涉及到的,在 Makefile 中都可以进行描述。换句话说,Makefile 可以使得我们的项目工程的编译变得自动化,不需要每次都手动输入一堆源文件和参数。
Cmake的所有语句都写在一个CMakeLists.txt的文件中,CMakeLists.txt文件确定后,直接使用Cmake命令进行运行,但是这个命令要指向CMakeLists.txt所在的目录,Cmake之后就会产生我们想要的makefile文件,然后再直接make就可以编译出可执行程序或者动态库。所以基本步骤就只有两步:(1)cmake生成MakeLists.txt文件;(2)make执行编译工作。
一个cmakelist文件主要包含以下的内容:
(1)声明 cmake 最低版本
cmake_minimun_required(VERSION 2.8)
(2)声明 cmake 工程名字
project( HelloSLAM )
加上上述指令,它会引入两个变量 demo_BINARY_DIR 和 demo_SOURCE_DIR,同时,cmake 自动定义了两个等价的变量 PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR。
PROJECT_SOURCE_DIR: 工程的根目录
PROJECT_BINARY_DIR: 运行 cmake 命令的目录,通常是 ${PROJECT_SOURCE_DIR}/build
PROJECT_NAME: 返回通过 project 命令定义的项目名称
CMAKE_CURRENT_SOURCE_DIR: 当前处理的 CMakeLists.txt 所在的路径
CMAKE_CURRENT_BINARY_DIR: target 编译目录
CMAKE_CURRENT_LIST_DIR: CMakeLists.txt 的完整路径
CMAKE_CURRENT_LIST_LINE: 当前所在的行
CMAKE_MODULE_PATH: 定义自己的 cmake 模块所在的路径,SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake),然后可以用INCLUDE命令来调用自己的模块
EXECUTABLE_OUTPUT_PATH: 重新定义目标二进制可执行文件的存放位置
LIBRARY_OUTPUT_PATH: 重新定义目标链接库文件的存放位置
(3)设置 cmake 编译模式
set( CMAKE_BUILD_TYPE “Debug” )
(¥)添加c++11标准支持
set(CMAKE_CXX_FLAGS “-std=c++11 -O3”)
参数CMAKE_CXX_FLAGS含义是: set compiler for c++ language而后面的-O3(是字母opq的o,大写的欧)是用来调节编译时的优化程度的,最高为-O3,最低为-O0(即不做优化)-Ox这个参数只有在CMake -DCMAKE_BUILD_TYPE=Release时有效,因为debug 版的项目生成的可执行文件需要有调试信息并且不需要进行优化,而 release 版的不需要调试信息但需要优化。
(5)添加变量
set( Sophus_INCLUDE_DIR ${PROJECT_SOURCE_DIR} )
(6)添加依赖
一般添加依赖项都需要解决三个问题:去哪里找头文件(.h等) 对应于GCC的参数 -I;去哪里找库文件(.so/.lib/.ddl等) 对应于GCC的参数 -L;需要链接的库文件名称 对应于GCC的参数 -l。
find_package
的作用就是去寻找该库的头文件位置、库文件位置以及库文件名称,并将其设为变量,返回提供给CMakeLists.txt其他部分使用。
find_package( Sophus REQUIRED )
find_package( Pangolin )
find_package( OpenCV REQUIRED )
(7)添加头文件
include_directories( “/usr/include/eigen3” )
include_directories( ${Pangolin_INCLUDE_DIRS} )
include_directories( ${Sophus_INCLUDE_DIRS} )
(8)设置链接库搜索目录
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/libs)
(9)添加一个可执行程序
add_executable( helloSLAM helloSLAM.cpp )
或者
aux_source_directory(. SRC_LIST) # 搜索当前目录下的所有.cpp文件
add_library(helloSLAM ${SRC_LIST})
或者
file(GLOB SRC_LIST "*.cpp")
file(GLOB SRC_PROTOCOL_LIST "protocol/*.cpp")
add_library(helloSLAM ${SRC_LIST} ${SRC_PROTOCOL_LIST})
其中,GLOB命令将所有匹配globbing-expressions(可选,假如不写,毛都匹配不到)的文件挑选出来,默认以字典顺序排序。
(10)构建静态库
add_library( hello_static STATIC libHelloSLAM.cpp )
(11)构建静动态库或者共享库
add_library( hello_shared SHARED libHelloSLAM.cpp )
(12)库文件链接到可执行程序上
target_link_libraries( useHello hello_shared )
target_link_libraries( imageBasics ${OpenCV_LIBS} )
target_link_libraries( joinMap ${OpenCV_LIBS} ${PCL_LIBRARIES} )
或者
link_libraries(libface.a)
link_libraries
用在add_executable
之前,target_link_libraries
用在add_executable
之后。
link_libraries
用来链接静态库,target_link_libraries
用来链接导入库,即按照header file + .lib + .dll方式隐式调用动态库的.lib库。
(13)指定安装地址
cmake -DCMAKE_INSTALL_PREFIX=/usr/local
(14)Debug和Release版本
CMAKE_BUILD_TYPE 指定基于make的产生器的构建类型(Release,Debug)
SET(CMAKE_CXX_FLAGS_DEBUG"ENVCXXFLAGS−O0−Wall−g−ggdb")
SET(CMAKE_CXX_FLAGS_RELEASE"ENVCXXFLAGS−O0−Wall−g−ggdb")
debug 版的项目生成的可执行文件需要有调试信息并且不需要进行优化,、
release 版的不需要调试信息但需要优化。这些特性在 gcc/g++ 中是通过编译时的参数来决定的,如果将优化程度调到最高需要设置参数-O3,最低是 -O0 即不做优化;添加调试信息的参数是 -g -ggdb ,如果不添加这个参数,调试信息就不会被包含在生成的二进制文件中。
(15)调试信息
打印信息,类似于 echo/printf ,主要用于查cmake文件的语法错误。
message(“mysql_use_test_sources : ${mysql_use_test_sources}”)
CmakeLists.txt的重要组成部分:拆分成以下几部分进行讲解。
1、表明Cmake所需要的最低版本:仅针对本项目
cmake_minimum_required(VERSION2.9 FATAL_ERROR)
2、定义需要的特殊变量(optional)(关于set的用法我目前没有细看,有心的同学自己钻研)
SET(sampleName MyApp)
如上面的例子:set(project_name cloud_viewer_PointXYZ)
3、查找我们构建工程所需要的package,这一步极其重要。
find_package(PCL 1.8.0 REQUIRED) #主要依赖的package
# //如果PCL被找到的话,那么将会生成几个包含package信息的Cmake环境变量,下面几个相关的变量就会被设置
PCL_FOUND:PCL找到后就设置为1,否则未设置
PCL_INCLUDE_DIRS:设置PCL头文件和依赖项头文件的安装路径
PCL_LIBRARIES:设置已构建和已安装PCL库的文件名
PCL_LIBRARY_DIRS:设置PCL库和第三方依赖的路径
PCL_VERSION:找到的PCL版本
PCL_COMPONENTS:列出所有的可用部分
PCL_DEFINITIONS:列出所有需要的预处理器定义和编译器标志
# 这些变量将会在后面的cmakescript中用到。
# 这些环境变量描述了package的外部头文件位置(include路径),
# 依赖的库文件的位置(lib),以及源程序的位置。
# 例如上面命令执行找到PCL后,将会创建环境变量PCL_INCLUDE_DIRS,其中包含指定PCL库头文件.h的查找路径;
# 创建环境变量PCL_LIBRARY_DIRS,其中包含指定PCL库的.lib文件的所在目录的路径.
# //REQUIRED表示如果没有找到,cmake会停止处理,并报告一个错误.
# 如果所要构建的project还依赖于其它的package,例如python那么可以采用命令:
find_package(PCL 1.8.0 REQUIRED COMPONENTS python),
# 此时python将作为PCLpackage的组成成分,它所对应的头文件include路径和lib库文件路径都将被
# 一起包含在PCL的对应环境变量PCL_INCLUDE_DIRS和PCL_LIBRARY_DIRS中,方便后面使用。
# 同样的,例如:find_package(PCL 1.8.0 REQUIRED COMPONENTS common io)
//找到PCL1.8.0及以上的版本
//REQUIRED表示找不到,cmake失败
//COMPONENTS表示对PCL模块的要求
# 要求一个模块:find_package(PCL 1.3 REQUIRED COMPONENTS io)
# 要求多个模块:find_package(PCL 1.3 REQUIRED COMPONENTS io common)
# 要求所有模块:find_package(PCL 1.3 REQUIRED)
4、指定构建project所需要的资源【 根据find_package结果进行 】
include_directories(${PCL_INCLUDE_DIRS})
# 包含头文件的位置。当编译一个需要第三方库的项目时,为了让 cmake知道你在项目里包含的外部头文件, 需使用include_directories()在–
# --我们的PCL_INCLUDE_DIRS宏(完全包含了我们所需要的头文件.h)中查找,让cmake查找到它可能包含的头文件。
link_directories(${PCL_LIBRARY_DIRS})
# 添加链接器的lib库文件路径,当编译一个需要第三方库的项目时,去哪找库文件(.so/.dll/.lib/.dylib/…)
# 使用 link_directories,让cmake在PCL_LIBRARY_DIRS宏中查找它可能包含的lib文件
add_definitions(${PCL_DEFINITIONS})
# 使用add_definitions 让cmake在PCL_DEFINITIONS中找出它可能包含的预处理器定义和编译器标志
# 除此之外也可以手动添加特殊路径如:【对应的头文件与库文件均需要添加】
include_directories("G:/Matlab/extern/include")
include_directories("C:/Program Files/MobileRobots/Aria/include")
link_directories("G:/Matlab/extern/lib/win32/microsoft")
link_directories("C:/Program Files/MobileRobots/Aria/lib")
5、从指定源文件构建可执行文件
add_executable (project_name cloud_viewer_PointXYZ.cpp)
# 告诉cmake我们尝试生成一个可执行文件,从一个单一的源文件cloud_viewer_PointXYZ.cpp生成一个名字是project_name的可执行文件。–
–CMake会注意生成的后缀名suffix (.exe on Windows platform and blank on UNIX) and the permissions。
# 该命令将从源文件cloud_viewer_PointXYZ.cpp构建可执行程序project_name.exe.
# 如果从多个源文件构建可执行程序则可以表示成:
add_executable (project_namemain.cpp test1.cpp test2.cpp)
或者
add_executable (project_namemain.cpp part.h grab.h interface.h test.cpp test.h)
6、为project构建library
add_library(${project_name} ${${project_name}_src})#默认创建共享library
7、指定可执行文件需要连接的库 设置要链接的库文件的名称
target_link_libraries (project_name ${PCL_LIBRARIES})#前一个参数为可执行文件的名字
target_link_libraries (project_name ${PCL_LIBRARIES} libeng.lib libmx.lib libmex.lib libmat.lib Aria.lib winmm.libwsock32.lib)
# 我们生成的可执行文件调用 PCL 函数. 到现在, 我们只包含了PCL头文件,所以编译器知道我们调用的方法。–
# --我们也需要让连接器知道我们链接所依赖的库文件。PCL通过使用PCL_LIBRARIES变量寻找相关库文件,target_link_libraries()宏对可执行文件与变量进行链接。
TARGET_LINK_LIBRARIES(
${project_name}
${MRPT_LIBS} # This is filled byFIND_PACKAGE(MRPT ...)
"" # Optional extra libs...
)