使用CMake构建库

Build library with CMake.

在大多数时候我们无法仅依赖一个源码树就完成整个项目,当你熟悉抽象之后,你的项目会逐渐变得越来越复杂,或是为了向其他开发人员隐去实现细节,通常会将项目的具体实现封装成库,供他人调用。

一个简单的类

PROJECT_SOURCE_DIR下新建lib/math目录,定义在math空间的operations类定义了四个数学运算:加减乘除。

// operations.hpp
#ifndef CMAKEHELLO_OPERATIONS_HPP
#define CMAKEHELLO_OPERATIONS_HPP
namespace math {
    class operations{
    public:
        int sum(const int &a, const int &b);
        int mult(const int &a, const int &b);
        int div(const int &a, const int &b);
        int sub(const int &a, const int &b);
    };
}
#endif //CMAKEHELLO_OPERATIONS_HPP



// operations.cpp
#include <exception>
#include <stdexcept>
#include <iostream>
#include "operations.hpp"
int math::operations::sum(const int &a, const int &b){
    return a + b;
}

int math::operations::mult(const int &a, const int &b){
    return a * b;
}

int math::operations::div(const int &a, const int &b){
    if(b == 0){
        throw std::overflow_error("Divide by zero exception");
    }
    return a/b;
}

int math::operations::sub(const int &a, const int &b){
    return a - b;
}

这时我们修改main.cpp让他包含operations.hpp并调用sum函数以便不需要再次实现它。

#include <iostream>
#include "lib/math/operations.hpp"
int main() {
    std::cout<<"Hello CMake!"<<std::endl;
        math::operations op;
        int sum = op.sum(3, 4);
    std::cout<<"Sum of 3 + 4 :"<<sum<<std::endl;
    return 0;
}

Library和Target一起编译

想要让main.cpp编译通过,最简单的方法就是将operations.cppoperations.hpp作为add_executable要编译的源码的一部分,一起编译:

cmake_minimum_required(VERSION 3.9.1)
project(CMakeHello)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")

set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin)

add_executable(cmake_hello main.cpp lib/math/operations.cpp lib/math/operations.hpp)

当要编译的源码变多之后,add_executable的参数列表将变得非常的长,更推荐的做法是将你要编译的源码文件定义为一个变量SOURCES,然后在add_executable命令中直接引用:

cmake_minimum_required(VERSION 3.9.1)
project(CMakeHello)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")

set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin)

set(SOURCES main.cpp 
            lib/math/operations.cpp 
            lib/math/operations.hpp)
            
add_executable(cmake_hello ${SOURCES})

单独编译math::operations

除了同时编译,更常见的做法是将operatioins.cpp编译成一个单独的库:

cmake_minimum_required(VERSION 3.9.1)
project(CMakeHello)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")

set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin)
# 设置编出的库位于build/lib下
set(LIBRARY_OUTPUT_PATH  ${CMAKE_BINARY_DIR}/lib)
message(${CMAKE_BINARY_DIR})

add_library(math SHARED lib/math/operations.cpp)    # 动态库
#add_library(math STATIC lib/math/operations.cpp)   # 静态库

add_executable(cmake_hello main.cpp)
target_link_libraries(cmake_hello math)

.so VS .a

不同平台下动态库文件的拓展名:

  • Windows: .dll

  • Mac OS X: .dylib

  • Linux: .so

不同平台下静态库文件的拓展名:

  • Windows: .lib

  • Mac OS X: .a

  • Linux: .a

动态库主要放置在编译主机的共享资源中,以确保多个应用程序能够使用它们。编译器的构建系统假设,动态库在执行期间在一个共享文件夹中,它将在执行期间处理这些来自动态库的资源,因此应用程序二进制大小将更小。但这也将导致一些性能上的损耗,因为每次执行都需要从动态库中加载指令。

静态库用于编译器将指令直接获取到应用程序二进制文件中,因此库中需要的所有代码都已经注入到最终的应用程序二进制文件中。这增加了目标对象的大小,但是性能得到了提高。并且使用静态库构建的应用程序不需要依赖于运行平台。

编译成sub-module

另外一种编译库的方法是,将库当作当前项目的一个子模块,在编译主程序之前先单独编译这个库。

lib/math目录下新建一个CMakeLists.txt

cmake_minimum_required(VERSION 3.9.1)

set(LIBRARY_OUTPUT_PATH  ${CMAKE_BINARY_DIR}/lib)

add_library(math SHARED operations.cpp)

在主程序的CMakeLists.txt中添加add_subdirectory命令:

cmake_minimum_required(VERSION 3.9.1)
project(CMakeHello)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")

set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin)
message(${CMAKE_BINARY_DIR})

add_subdirectory(lib/math)

add_executable(cmake_hello main.cpp)
target_link_libraries(cmake_hello math)

add_subdirectory命令使得CMake进入该目录并依照该目录下的CMakeLists.txt单独进行构建。

注:lib/math/CMakeLists.txt中指定的库输出路径为${CMAKE_BINARY_DIR/lib}这意味着编译输出的libmath.so将位于主程序构建目录的lib目录下,假如你使用外部编译并将编译目录命名为build,那libmath.so将位于build/lib目录下,并且会为lib/math创建自己的构建空间:

.
├── bin
│   └── cmake_hello
├── CMakeCache.txt
├── CMakeFiles
│   ├── 3.21.0-rc3
│   │   ├── CMakeCCompiler.cmake
│   │   ├── CMakeCXXCompiler.cmake
│   │   ├── CMakeDetermineCompilerABI_C.bin
│   │   ├── CMakeDetermineCompilerABI_CXX.bin
│   │   ├── CMakeSystem.cmake
│   │   ├── CompilerIdC
│   │   │   ├── a.out
│   │   │   ├── CMakeCCompilerId.c
│   │   │   └── tmp
│   │   └── CompilerIdCXX
│   │       ├── a.out
│   │       ├── CMakeCXXCompilerId.cpp
│   │       └── tmp
│   ├── cmake.check_cache
│   ├── CMakeDirectoryInformation.cmake
│   ├── cmake_hello.dir
│   │   ├── build.make
│   │   ├── cmake_clean.cmake
│   │   ├── compiler_depend.internal
│   │   ├── compiler_depend.make
│   │   ├── compiler_depend.ts
│   │   ├── DependInfo.cmake
│   │   ├── depend.make
│   │   ├── flags.make
│   │   ├── link.txt
│   │   ├── main.cpp.o
│   │   ├── main.cpp.o.d
│   │   └── progress.make
│   ├── CMakeOutput.log
│   ├── CMakeTmp
│   ├── Makefile2
│   ├── Makefile.cmake
│   ├── progress.marks
│   └── TargetDirectories.txt
├── cmake_install.cmake
├── lib
│   ├── libmath.so
│   └── math
│       ├── CMakeFiles
│       │   ├── CMakeDirectoryInformation.cmake
│       │   ├── math.dir
│       │   │   ├── build.make
│       │   │   ├── cmake_clean.cmake
│       │   │   ├── compiler_depend.internal
│       │   │   ├── compiler_depend.make
│       │   │   ├── compiler_depend.ts
│       │   │   ├── DependInfo.cmake
│       │   │   ├── depend.make
│       │   │   ├── flags.make
│       │   │   ├── link.txt
│       │   │   ├── operations.cpp.o
│       │   │   ├── operations.cpp.o.d
│       │   │   └── progress.make
│       │   └── progress.marks
│       ├── cmake_install.cmake
│       └── Makefile
└── Makefile

Last updated