CMake:构建、链接静态库和动态库

2023-05-16

CMake:构建、链接静态库和动态库

    • 导言
    • 一、多目录多文件CMake构建方式
      • 1. 项目结构
      • 2. message.h
      • 3. message.cpp
      • 4. hello_world.cpp
      • 5. CMakeLists.txt
      • 6. 构建及编译
    • 二、静态库和动态库简介
      • 1. 静态库
      • 2. 动态库
    • 三、编译和链接静态库
      • 1. 项目结构
      • 2. message-module下的CMakeLists.txt
      • 3. 根目录下的CMakeLists.txt
      • 4. 构建及编译
    • 四、 只链接三方库(静态库)
      • 1. 项目结构
      • 2. CMakeLists.txt
      • 3. 构建及编译
    • 五、编译和链接动态库
      • 1. GNU/Linux平台上动态库的编译和链接
      • 2. GNU/Linux平台上只链接三方库(动态库)
      • 3. Windows平台上动态库的编译和链接
        • (1) 项目结构
        • (2) message.h
        • (3) message.cpp
        • (4) message-module下的CMakeLists.txt
        • (5) 根目录下的CMakeLists.txt
      • 4. Windows平台上只链接三方库(动态库)
        • (1) 项目结构
        • (2) CMakeLists.txt
      • 六、补充

导言

上一篇,我们开启了CMake学习之旅—单个源文件编译为可执行文件。本节我们将学习编译和链接动态库、静态库。

在实际项目构建过程中,单文件、单目录(即只有根目录)是很难满足构建大型项目的需求,所以我们首先将学习多目录多文件的CMake构建方式,然后开始学习如何构建和链接静态库、动态库。注意,在编写动态库时,GNU/Linux和Windows编写C++代码略有不同,这将在具体章节进行具体讲解。

最后我们针对编译生成静态库和动态库的命令,对一些其他参数做进一步探索。

一、多目录多文件CMake构建方式

1. 项目结构

.
├── include
│   └── message.h
├── src
│   └── message.cpp
├── hello_world.cpp
└── CMakeLists.txt

项目结构是为了让我们开发人员对项目更加清晰,使代码结构更加清晰(模块化)。一般我们的项目比较简单时,可以构建为如上的项目结构。但是在构建大型项目时,项目结构会更加复杂,具体请参考下节内容。

这里我们构建了include目录和src目录,include目录主要存放的是CPP文件的头文件,即函数的声明,为使用它的文件提供API。src目录主要是存放的函数的具体实现。

源码地址:https://gitee.com/jiangli01/tutorials/tree/master/cmake-tutorial/chapter1/02

2. message.h

#ifndef MESSAGE_HEADER_H_
#define MESSAGE_HEADER_H_

#include <iostream>
#include <string>

class Message {
 public:
  Message() {}
  
  void Print(const std::string &message);
};
#endif // ! MESSAGE_HEADER_H_

3. message.cpp

#include "message.h"

void Message::Print(const std::string &message) {
  std::cout << message << std::endl;
}

4. hello_world.cpp

#include "message.h"

int main() {
  Message message;
  message.Print("Hello, CMake World!");
  message.Print("Goodbye, CMake World!");
  
  return EXIT_SUCCESS;
}

5. CMakeLists.txt

cmake_minimum_required(VERSION 3.10 FATAL_ERROR)

project(hello-world LANGUAGES CXX)

include_directories(
    ${CMAKE_SOURCE_DIR}/include
)

file(GLOB HEADER ${CMAKE_SOURCE_DIR}/include/*.h)
file(GLOB SOURCE ${CMAKE_SOURCE_DIR}/src/*.cpp)

add_executable(
    ${PROJECT_NAME}
    ${HEADER}
    ${SOURCE}
    ${CMAKE_SOURCE_DIR}/hello_world.cpp
) 
include_directories(
    ${CMAKE_SOURCE_DIR}/include
)

include目录下的所有文件包含进来,这样include目录下的message.h将会被包含到整个项目中。如果我们在细分目录中使用包含某一模块的头文件,我们可以在具体模块的CMakeLists.txt中使用该命令,且要包含的头文件的可见性只有该模块,其他模块不可见,具体使用方法,请参考下节内容。

file(GLOB HEADER ${CMAKE_SOURCE_DIR}/include/*.h)
file(GLOB SOURCE ${CMAKE_SOURCE_DIR}/src/*.cpp)

如果include目录和src目录中有多个头文件和源文件,使用如上命令可以将所有头文件集合到HEADERSOURCE自定义宏定义中,使用时的命令为${HEADER}${SOURCE}

add_executable(
    ${PROJECT_NAME}
    ${HEADER}
    ${SOURCE}
    ${CMAKE_SOURCE_DIR}/hello_world.cpp
) 

这将结合include目录下的文件和src目录下的文件以及hello_world.cpp生成名为hello-world的可执行文件。

6. 构建及编译

mkdir build
cd build
cmake ..
-- The CXX compiler identification is GNU 9.4.0
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/jiangli/repo/tutorials/cmake-tutorial/chapter1/02/build
make
Scanning dependencies of target hello-world
[ 33%] Building CXX object CMakeFiles/hello-world.dir/src/message.cpp.o
[ 66%] Building CXX object CMakeFiles/hello-world.dir/hello_world.cpp.o
[100%] Linking CXX executable hello-world
[100%] Built target hello-world

上一篇我们没有讲将执行cmake命令后生成的MakeFile文件,其如何构建出可执行文件的具体操作,只是简单的说MakeFile需要make命令执行。

在我们执行完cmake ..后,将生成MakeFile文件,然后执行make后便可以生成可执行文件。

这里我们进行补充说明:如果我们在GNU/Linux上,执行CMake ..后会生成MakeFile文件,然后执行make命令即可生成可执行文件;在Windows上,执行cmake ..后会生成sln文件,需要使用VS进行打开,然后对其进行生成操作。Windows生成sln文件后的具体操作过程请参考最后一些补充内容。除此之外,我们可以执行以下命令,不分平台直接构建出可执行文件:

cmake --build .

二、静态库和动态库简介

首先,如果对程序的生命周期的不清楚,请先移步这里进行学习。

1. 静态库

在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。这个链接方式为静态链接,所需要的.o(unix系统)称为静态库。

  • 静态库对函数库的链接是放在编译时期完成的。
  • 程序在运行时与函数库再无瓜葛,移植方便。
  • 浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接成一个可执行文件。
  • 静态库对程序的更新、部署和发布会带来麻烦。如果静态库libxxx.o更新了,所有使用它的应用程序都需要重新编译、发布给用户。

2. 动态库

动态库在程序编译时并不会链接到目标代码中,而是在程序运行时才被载入。不同的应用程序如果调用相同的库,那么在内存只需要有一份该共享库的实例,规避了空间浪费。

动态库在程序运行时才被载入,也解决了静态库对程序的更新、部署和发布带来的问题,用户只需要更新动态库即可,增量更新。

Windows与Linux执行文件格式不同,在创建动态库的时候有一些差异:

  • 在Windows系统下的执行文件格式是PE格式,动态库需要一个DllMain函数做初始化的入口,通常在导出函数的声明时需要有_declspec(dllexport)关键字。
  • Linux下gcc编译的执行文件默认是ELF格式,不需要初始化入口,亦不需要函数做特别的声明,编写比较方便

三、编译和链接静态库

1. 项目结构

.
├── message-module  
│   ├── include
│   │   └── message.h
│   ├── src
│   │   └── message.cpp
│   └── CMakeLists.txt
├── hello_world.cpp
└── CMakeLists.txt

在实际的项目开发过程中,我们的项目结构往往会由很多个模块组成,每个模块通过一个单独的CMakeLists.txt去控制,最后在根目录下的CMakeLists.txt中将各个模块组合使用。

本项目中为了简化学习,只构建了一个message-module模块,构建多个模块的方式同理。其中项目中的所有CPP源文件与第一节内容相同,这里就不展开描述了。

源码地址:https://gitee.com/jiangli01/tutorials/tree/master/cmake-tutorial/chapter1/03

2. message-module下的CMakeLists.txt

include_directories(
    ${CMAKE_SOURCE_DIR}/include
)

file(GLOB HEADER ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h)
file(GLOB SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)

add_library(
    test_message
    STATIC
    ${HEADER}
    ${SOURCE}
)
add_library(
    test_message
    STATIC
    ${HEADER}
    ${SOURCE}
)

add_library生成必要的构建指令,将指定的源码编译到库中。第一个参数是目标名。整个项目中,可使用相同的名称来引用库。生成的库的实际名称将由CMake通过在前面添加前缀lib和适当的扩展名作为后缀来形成。生成库是根据第二个参数(STATIC或SHARED)和操作系统确定的,本项目是将目标文件生成静态库。

3. 根目录下的CMakeLists.txt

cmake_minimum_required(VERSION 3.10 FATAL_ERROR)

project(hello-world LANGUAGES CXX)

# 设置可执行文件到bin文件夹下
set(EXECUTE_FILE ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${EXECUTE_FILE})

# 设置静态库到lib文件夹下
set(LIB_FILE ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY  ${LIB_FILE})

include_directories(
    ${CMAKE_SOURCE_DIR}/message-module/include
)

add_subdirectory(
    ${CMAKE_SOURCE_DIR}/message-module
)

add_executable(
    ${PROJECT_NAME}
    ${CMAKE_SOURCE_DIR}/hello_world.cpp
) 

target_link_libraries(
    ${PROJECT_NAME}
    test_message   
)
# 设置可执行文件到bin文件夹下
set(EXECUTE_FILE ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${EXECUTE_FILE})

# 设置静态库到lib文件夹下
set(LIB_FILE ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY  ${LIB_FILE})

在构建项目时,我们为了使得项目结构更加清晰,使得生成的可执行文件、静态库以及动态库等文件能够存放在合适的位置。这样的构建方式有助于我们在项目重构、项目优化、debug的时候逻辑更加清晰。

include_directories(
    ${CMAKE_SOURCE_DIR}/message-module/include
)

这个命令同第一节内容,因为hello_world.cpp要使用message-module模块的API,且与该CMakeLists.txt在相同层级的目录,所以需要将message-module模块的API包含进去。

如果hello_world.cpp中使用到了多个模块,则此处可以包含多个模块的API:

include_directories(
    ${CMAKE_SOURCE_DIR}/message-module/include
    ${CMAKE_SOURCE_DIR}/xxx-module/include
)
add_subdirectory(
    ${CMAKE_SOURCE_DIR}/message-module
)

将我们的message-module添加进来进行编译,这个函数命令将寻找message-module目录下的CMakeLists.txt,如果该目录下没有CMakeLists.txt将报错。

由于在本项目中,hello_world.cpp要使用message-module模块中编译生成的静态库,所以add_subdirectory命令将message-module添加到项目中的的顺序必须要先于add_executable命令。

add_executable(
    ${PROJECT_NAME}
    ${CMAKE_SOURCE_DIR}/hello_world.cpp
) 

target_link_libraries(
    ${PROJECT_NAME}
    test_message   
)

add_executable命令将hello_world.cpp编译成可执行文件,其名字为项目名称hello-world,该可执行文件使用target_link_libraries命令将message-module模块下编译生成的静态库test_message链接到可执行文件中。

注意:在子模块message-module中编译生成的test_message是全局可见的,即任何模块或者根目录下的CMakeLists.txt都可以直接使用test_message进行调用。

4. 构建及编译

mkdir build
cd build
cmake ..
-- The CXX compiler identification is GNU 9.4.0
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/jiangli/repo/tutorials/cmake-tutorial/chapter1/03/build
# cmake --build .
make 
Scanning dependencies of target test_message
[ 25%] Building CXX object message-module/CMakeFiles/test_message.dir/src/message.cpp.o
[ 50%] Linking CXX static library ../lib/libtest_message.a
[ 50%] Built target test_message
Scanning dependencies of target hello-world
[ 75%] Building CXX object CMakeFiles/hello-world.dir/hello_world.cpp.o
[100%] Linking CXX executable bin/hello-world
[100%] Built target hello-world

可以通过编译日志看到,首先编译了message-module模块,并将编译生成的libtest_message.a存档到了../lib/,即build文件夹中的lib目录中。然后链接hello-world所需要的依赖项,此时便将test-message链接到了hello-world中,最终生成可执行文件hello-world,并将其存放到bin目录中,即build文件夹下的bin目录。

四、 只链接三方库(静态库)

我们在构建实际项目过程中,一个项目往往需要链接许多的三方库,抑或是我们将自己的算法以静态库的形式发布,通常需要为我们的项目链接三方库。本节讲其中的一种,后续涉及到三方库的链接将讲述所有链接的方式。关于third-party模块下include文件夹下的message.h头文件与前面相同,lib文件夹下的libtest_message.a是第三节编译生成的静态库。

1. 项目结构

.
├── third-party
│   ├── include
│   │   └── message.h
│   └── lib
│       └── libtest_message.a
├── hello_world.cpp
└── CMakeLists.txt

一般,我们将三方库放到项目中一个third-party的文件夹下,当然你也可以随意命名。三方库third-party中包含includelib分别存放三方库的API和静态库。
源码地址:https://gitee.com/jiangli01/tutorials/tree/master/cmake-tutorial/chapter1/04

2. CMakeLists.txt

cmake_minimum_required(VERSION 3.10 FATAL_ERROR)

project(hello-world LANGUAGES CXX)

# 设置可执行文件到bin文件夹下
set(EXECUTE_FILE ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${EXECUTE_FILE})

set(TEST_MESSAGE ${CMAKE_SOURCE_DIR}/third-party/lib/libtest_message.a)

include_directories(
    ${CMAKE_SOURCE_DIR}/third-party/include
)

add_executable(
    ${PROJECT_NAME}
    ${CMAKE_SOURCE_DIR}/hello_world.cpp
) 

target_link_libraries(
    ${PROJECT_NAME}
    ${TEST_MESSAGE}   
)
set(TEST_MESSAGE ${CMAKE_SOURCE_DIR}/third-party/lib/libtest_message.a)

将三方库中的静态库定义为TEST_MESSAGE,方便后续使用${TEST_MESSAGE}进行调用。当然你也可以直接在target_link_libraries命令中使用${CMAKE_SOURCE_DIR}/third-party/lib/libtest_message.a进行链接,但是这么做是不推荐的。如果多个模块都使用到了该库,那么定义为宏的方式更加方便和清晰。

今后,我们都将定义出来的宏统一采用了大写,意和CMake自身变量命名对其。

3. 构建及编译

mkdir build
cd build
cmake ..
-- The CXX compiler identification is GNU 9.4.0
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/jiangli/repo/tutorials/cmake-tutorial/chapter1/04/build
cmake --build .
Scanning dependencies of target hello-world
[ 50%] Building CXX object CMakeFiles/hello-world.dir/hello_world.cpp.o
[100%] Linking CXX executable bin/hello-world
[100%] Built target hello-world

五、编译和链接动态库

动态库的编写需要区分平台,在GNU/Linux平台上,动态库的编写和调用与静态库没有差别,但是在Windows平台上动态库的编写和调用需要做一定的修改。

1. GNU/Linux平台上动态库的编译和链接

在GNU/Linux上生成动态库的方法和静态库生成的方法类似,其目录结构等都与静态库相同,只有在使用add_library命令时,参数STATIC改为SHARE即可,相关项目结构和CMakeLists.txt如下。

.
├── message-module  
│   ├── include
│   │   └── message.h
│   ├── src
│   │   └── message.cpp
│   └── CMakeLists.txt
├── hello_world.cpp
└── CMakeLists.txt
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)

project(hello-world LANGUAGES CXX)

# 设置可执行文件到bin文件夹下
set(EXECUTE_FILE ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${EXECUTE_FILE})

# 设置动态库到lib文件夹下
set(LIB_FILE ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY  ${LIB_FILE})

include_directories(
    ${CMAKE_SOURCE_DIR}/message-module/include
)

add_subdirectory(
    ${CMAKE_SOURCE_DIR}/message-module
)

add_executable(
    ${PROJECT_NAME}
    ${CMAKE_SOURCE_DIR}/hello_world.cpp
) 

target_link_libraries(
    ${PROJECT_NAME}
    test_message   
)

这里我们设置动态库存放的路径的宏为CMAKE_LIBRARY_OUTPUT_DIRECTORY
源码地址:https://gitee.com/jiangli01/tutorials/tree/master/cmake-tutorial/chapter1/05

2. GNU/Linux平台上只链接三方库(动态库)

在GNU/Linux上链接动态库的方法和静态库生成的方法类似,其目录结构等都与静态库相同,只有在使用add_library命令时,参数STATIC改为SHARE即可,相关项目结构和CMakeLists.txt如下。

.
├── third-party
│   ├── include
│   │   └── message.h
│   └── lib
│       └── libtest_message.so
├── hello_world.cpp
└── CMakeLists.txt
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)

project(hello-world LANGUAGES CXX)

# 设置可执行文件到bin文件夹下
set(EXECUTE_FILE ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${EXECUTE_FILE})

set(TEST_MESSAGE ${CMAKE_SOURCE_DIR}/third-party/lib/libtest_message.so)

include_directories(
    ${CMAKE_SOURCE_DIR}/third-party/include
)

add_executable(
    ${PROJECT_NAME}
    ${CMAKE_SOURCE_DIR}/hello_world.cpp
) 

target_link_libraries(
    ${PROJECT_NAME}
    ${TEST_MESSAGE}   
)

源码地址:https://gitee.com/jiangli01/tutorials/tree/master/cmake-tutorial/chapter1/06

3. Windows平台上动态库的编译和链接

注意:通过实践发现,Windows中CMAKE_LIBRARY_OUTPUT_DIRECTORY没有作用。在Windows中生成的动态库将会自动生成到可执行文件所在的目录。

前面我们说Windows平台中生成动态库的源码和静态库是不同的,在Windows平台中,在导出动态库时除了会生成.dll动态库之外,还会生成一个.lib文件。注意,这个.lib文件和静态库的.lib文件时不同的,它里面并不保存代码生成的二进制文件,而是所有需要导出符号的符号表。因此这个.lib文件和编译生成的的静态库.lib相比较而言会小的多。

符号表是需要是需要我们在编写源码时进行指定的,如果我们将一个符号导出(符号可以指类、函数等各种类型),需要在其前面加上__declspec(dllexport)标志,这样这个符号的相关信息就会在导出的.lib中的符号表中了。

如果在源码中没有任何的__declspec(dllexport),依然可以成功的编译出动态库,但是并不会生成保存符号表的.lib文件。


class __declspec(dllexport) Message {

public:
	Message() {}
	
    void Print(const std::string &message);
};

除了导出符号标识符__declspec(dllexport)以外,作为用户使用动态库的时候,对应的头文件的符号还需要__declspec(dllimport)标识符来表示这个符号是从动态库导入的。


class __declspec(dllimport) Message {

public:
	Message() {}
	
    void Print(const std::string &message);
};

一般,一个库文件我们并不想对导入和导出分别写两个几乎同样的头文件,因此可以使用宏定义来代替直接使用__declspec(dllexport)__declspec(dllimport)关键字。


#ifndef MESSAGE_HEADER_H_
#define MESSAGE_HEADER_H_

#ifdef SHARED_LIB_EXPORT

#define SHARED_LIB_EXPORT __declspec(dllexport)

#else

#define SHARED_LIB_EXPORT __declspec(dllimport)

#endif

class SHARED_LIB_EXPORT Message {

public:
	Message() {}
    void Print(const std::string &message);
};

#endif // ! MESSAGE_HEADER_H_

这样我们只需要在编译(导出)这个库的时候,给编译器添加 SHARED_LIB_EXPORT 宏。而在使用该库的时候什么都不定义即可。

我们通常编写一个头文件来专门管理SHARED_LIB_EXPORT宏定义。为了使得我们的代码在Linux中平台以及静态库的情况,我们的头文件编写如下:

#ifndef EXPORT_LIB_HEADER_H_
#define EXPORT_LIB_HEADER_H_

#ifdef SHARED_LIB_BUILD
#ifdef _WIN32
#ifdef SHARED_LIB_EXPORT
#define SHARED_LIB_API __declspec(dllexport)
#else
#define SHARED_LIB_API __declspec(dllimport)
#endif  // SHARED_LIB_EXPORT
#else
#define SHARED_LIB_API
#endif  // _WIN32
#else
#define SHARED_LIB_API
#endif  // SHARED_LIB_BUILD

#endif // ! EXPORT_LIB_HEADER_H_

我们除了使用SHARED_LIB_API宏定义来判断是否导出为动态库以外ia,还使用了编译器自带的_WIN32宏来判断是实在windows平台上以及使用。SHARED_LIB_BUILD来判断是否正在编译动态库。

有了这个头文件之后,我们只需要在导出符号表的头文件中包含该头文件,就可以使用SHARED_LIB_API宏定义了。

除此之外,上述的头文件可以通过CMake提供的GenerateExportHeader命令自动生成。关于该命令的使用在后续介绍中会详细的进行探索。

(1) 项目结构

.
├── message-module  
│   ├── include
│   │	└── message.h
│   ├── src
│   │   └── message.cpp
│   └── CMakeLists.txt
├── hello_world.cpp
└── CMakeLists.txt

本项目中相对于之前的项目结构,在message-module中多了头文件export_lib管理不同平台是否生成动态库。
源码地址:https://gitee.com/jiangli01/tutorials/tree/master/cmake-tutorial/chapter1/07

(2) message.h

#ifndef MESSAGE_HEADER_H_
#define MESSAGE_HEADER_H_

#include <iostream>
#include <string>

class __declspec(dllexport) Message {
 public:
  Message() {}

  void Print(const std::string &message);
};
#endif // ! MESSAGE_HEADER_H_

(3) message.cpp

#include "message.h"

void Message::Print(const std::string &message) {
  std::cout << message << std::endl;
}

(4) message-module下的CMakeLists.txt

include_directories(
    ${CMAKE_SOURCE_DIR}/include
)

file(GLOB HEADER ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h)
file(GLOB SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)

add_library(test_message SHARED ${SOURCE})

add_library(test_message::test_message ALIAS test_message)

target_include_directories(test_message
    PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>/include
    $<INSTALL_INTERFACE:include>
)

set_target_properties(test_message PROPERTIES 
    CXX_STANDARD 11
    CMAKE_CXX_STANDARD_REQUIRED True
)

install(TARGETS test_message
        EXPORT message_export_target
        RUNTIME DESTINATION "bin"
        LIBRARY DESTINATION "lib"
        ARCHIVE DESTINATION "lib")

install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ 
        DESTINATION "include"
        FILES_MATCHING PATTERN "*.h")
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ 
        DESTINATION "include"
        FILES_MATCHING PATTERN "*.hpp")
add_library(test_message::test_message ALIAS test_message)

添加别名,以便库可以在构建树中使用,例如在测试时。

target_include_directories(test_message
    PUBLIC
	$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>/include
    $<INSTALL_INTERFACE:include>
)

设置 test_message 的包含路径。其中,PUBLIC 表示这些头文件路径将会被暴露给该目标的依赖项,即其他目标可以通过依赖该目标来访问这些头文件路径。

BUILD_INTERFACE表示在构建时使用的头文件路径;INSTALL_INTERFACE表示在安装时使用的头文件路径,即将该目标安装到其他地方时,头文件将会被安装到 include 目录下。

综上,这段代码的作用是将当前项目的根目录添加到 “test_message” 目标的头文件包含路径中,以便在编译和安装时能够正确地访问这些头文件。

set_target_properties(test_message PROPERTIES 
    CXX_STANDARD 11
    CMAKE_CXX_STANDARD_REQUIRED True
)

设置 test_message 的相关属性,关于target_include_directoriesset_target_properties的具体使用情况,我们将在以后做详细讲解。

install(TARGETS test_message
        EXPORT message_export_target
        RUNTIME DESTINATION "bin"
        LIBRARY DESTINATION "lib"
        ARCHIVE DESTINATION "lib")

安装一个名为 “test_message” 的目标(在Windows上需要将编译出来的sln文件使用vs打开,然后再属性栏中的INSTALL右键生成,即可执行install命令)。其中,TARGETS表示要安装的目标名称,即 test_messageEXPORT message_export_target 表示将该目标导出到一个名为 “message_export_target” 的 CMake 配置文件中,以便其他项目可以使用该目标。

注意:我们在根目录下已经指定了’install’安装目录的根目录。

RUNTIME DESTINATION ${CMAKE_SOURCE_DIR}/output/bin 表示将该目标的可执行文件安装到当前项目的 bin 目录下。

LIBRARY DESTINATION ${CMAKE_SOURCE_DIR}/output/lib 表示将该目标的共享库文件安装到 当前项目的lib 目录下。

ARCHIVE DESTINATION ${CMAKE_SOURCE_DIR}/output/lib) 表示将该目标的静态库文件安装到 当前项目的lib 目录下。
注意:在Windows构建动态库时,动态库dll会被安装到当前项目的bin目录下,目标文件.lib文件会被安装到项目的lib目录下。

综上,这段代码的作用是将 “test_message” 目标的可执行文件、共享库文件和静态库文件安装到指定的目录下,并将该目标导出到一个 CMake 配置文件中,以便其他项目可以使用该目标。关于生成cmake相关配置文件详见下一篇内容。本项目仅用此命令做输出库的相关文件的功能。

install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ 
        DESTINATION ${CMAKE_SOURCE_DIR}/output/include
        FILES_MATCHING PATTERN "*.h")
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ 
        DESTINATION ${CMAKE_SOURCE_DIR}/output/include
        FILES_MATCHING PATTERN "*.hpp")

将当前目录下的include目录中的.hpp.h安装到当前项目下的output目录下的include文件中。关于install命令的具体使用方法,我们将以后做详细解释。

(5) 根目录下的CMakeLists.txt

cmake_minimum_required(VERSION 3.10 FATAL_ERROR)

project(hello-world LANGUAGES CXX)

# 设置可执行文件到bin文件夹下
set(EXECUTE_FILE ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${EXECUTE_FILE})

# 设置动态库到lib文件夹下
set(LIB_FILE ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY  ${LIB_FILE})

if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
  set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/output/)
endif()

add_subdirectory(
    ${CMAKE_SOURCE_DIR}/message-module
)

add_executable(
    ${PROJECT_NAME}
    ${CMAKE_SOURCE_DIR}/hello_world.cpp
) 

target_link_libraries(
    ${PROJECT_NAME}
    test_message   
)
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
  set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/output/)
endif()

如果没有指定install的目录,则设置install的目录为${CMAKE_SOURCE_DIR}/output/

4. Windows平台上只链接三方库(动态库)

Windows平台上链接动态库的使用方法和GNU/Linux平台上有所不同,我们在链接时需要将符号表文件lib进行连接,然后将对应的动态库文件dll文件拷贝到环境变量或者可执行文件所在的目录下,可知行文件才可以正常过执行。

(1) 项目结构

.
├── third-party
│   ├── include
│   │   └── message.h
│   ├── lib
│   │   └── test_message.lib
│	└── bin
│		└── test_message.dll
├── hello_world.cpp
└── CMakeLists.txt

项目中使用的third-party使用的是上一节内容中在Windows上生成的动态库。文件hello_world.cpp与上一节内容相同,我们不再对其进行描述。
源码地址:https://gitee.com/jiangli01/tutorials/tree/master/cmake-tutorial/chapter1/08

(2) CMakeLists.txt

cmake_minimum_required(VERSION 3.10 FATAL_ERROR)

project(hello-world LANGUAGES CXX)

set(EXECUTE_FILE ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${EXECUTE_FILE})

set(TEST_MESSAGE ${CMAKE_SOURCE_DIR}/third-party/lib/test_message.lib)

include_directories(
    ${CMAKE_SOURCE_DIR}/third-party/include
)

add_executable(
    ${PROJECT_NAME}
    ${CMAKE_SOURCE_DIR}/hello_world.cpp
) 

target_link_libraries(
    ${PROJECT_NAME} 
    ${TEST_MESSAGE} 
)

if (MSVC)
  file(GLOB MODEL "${CMAKE_SOURCE_DIR}/third-party/bin/*.dll")
  add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
                     COMMAND ${CMAKE_COMMAND} -E copy_if_different
                     ${MODEL}
                     $<TARGET_FILE_DIR:${PROJECT_NAME}>)
endif()
set(TEST_MESSAGE ${CMAKE_SOURCE_DIR}/third-party/lib/test_message.lib)

链接三方库的符号表

if (MSVC)
  file(GLOB MODEL "${CMAKE_SOURCE_DIR}/third-party/bin/*.dll")
  add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
                     COMMAND ${CMAKE_COMMAND} -E copy_if_different
                     ${MODEL}
                     $<TARGET_FILE_DIR:${PROJECT_NAME}>)
endif()

将我们之前生成的动态库文件dll文件手动复制到可执行文件所在的目录中。关于add_custom_command用法我们之后章节将作详细详解。

六、补充

在window上使用vs生成可执行文件和执行install命令。
首先,我们首先新建一个build目录,并进入该目录。

mkdir build
cd build

然后,然后使用cmake进行构建项目。

cmake ..

然后在build目录下可以看到sln文件,使用vs打开。

构建所有:在vs中的解决方案资源管理器中右键ALL_BUILD,然后点击生成

编译生成hello-world进程,右键hello-world,然后点击生成。如果我们使用CMake在一个项目中生成了多个进程,我们在测试某一个进程时,在对应的进程上右键设为启动项目即可。

执行install安装命令:在INSTALL上右键,然后点击生成即可。注意:只有当我们的CMake中有install命令时,VS中才会出现INTALL选项。

最后,是否学习完动态库和静态库后感觉我们在 不同平台上写的相关CMake文件和对应的CPP代码有所差别而有所烦恼?下一篇我们将一起学习一下如何将不同平台用同一套同一套模板去既可以生成静态库又可以生成不同平台的动态库!
更多请关注微信公众号【Hope Hut】:
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

CMake:构建、链接静态库和动态库 的相关文章

  • 有没有办法在 CMake 配置中禁止“实验性”C++17?

    我在 CMakeLists txt 中设置了以下内容 set CMAKE CXX STANDARD 17 set CMAKE CXX STANDARD REQUIRED ON set CMAKE CXX EXTENSIONS OFF 然而
  • 如何在 C++ 项目的 Cmake 文件上添加 Mac OS 框架

    我正在尝试将外部库 Vulkan 添加到我的项目中 这个库是预编译的并且有一个框架 我的项目树 build source Entry main cpp include ext vulkan macos include lib Framewo
  • CMake“项目”指令的正确用法是什么

    我有一个很大的代码库 它构建了几十个库和几个可执行文件 代码库按层次结构进行分解 并且几乎在每个级别都构建了库 我已经仔细检查并在每个目录中放置了一个 CMakeLists txt 文件来构建每个库 在每个 CMakeLists txt 中
  • CMake:用于Android交叉编译的FIND_PACKAGE(Threads)

    我正在使用 Android NDK 和 Cmake 生成项目的共享库 我正在将现有项目从 Ubuntu 移植到 Android 现在我需要移植一些可执行文件 我成功编译了所有需要的可执行文件Threads图书馆 在CMakeList txt
  • 用于区分调试和发布版本的 CMake 变量或属性

    我想为调试和发布版本设置不同的 CMake 变量 我尝试像这样使用 CMAKE CFG INTDIR IF CMAKE CFG INTDIR STREQUAL Debug SET TESTRUNNER DllPlugInTesterd dl
  • 为 CMake 中的子目录生成“干净”目标

    我想生成一个clean子目录的目标 我的项目结构是这样的 app A B lib A B C 有时我只想在 app A 上运行干净 并且不想清理库 是否可以告诉 CMake 生成clean每个目录的目标 或者像这样的自定义目标应用程序清理哪
  • 在cmake中设置PKG_CONFIG_PATH

    我已经在本地构建了 opencv 并将其安装到本地目录 不是系统默认目录 opencv pc存在于该本地文件夹中的 pkgconfig 文件夹下 我怎样才能找到这个opencv pc来自 cmake 因为我想从我的程序链接并包含 openc
  • 在 CMake 中,CHECK_INCLUDE_FILE_CXX 如何工作?

    以下代码不打印任何内容 CHECK INCLUDE FILE CXX glog logging h GLOG INCLUDE IF GLOG INCLUDE MESSAGE YY ENDIF GLOG INCLUDE 但我设置了以下环境变量
  • 如何使我的单元测试适应 cmake 和 ctest?

    到目前为止 我已经使用了一个临时的单元测试程序 基本上是由批处理文件自动运行的全部单元测试程序 尽管其中很多都明确地检查了他们的结果 但还有更多的作弊行为 他们将结果转储到版本控制的文本文件中 测试结果中的任何更改都会被颠覆标记 我可以轻松
  • 在 C 中使用 pow 时,CMake 可以检测是否需要链接到 libm 吗?

    对于某些编译器 using powC 程序中的某些其他函数需要链接到m library https stackoverflow com q 8671366 1959975 但是 某些编译器不需要这样做 并且在链接到m图书馆 C 也存在几乎相
  • 如何使用 CMake 安装文件层次结构?

    我使用以下方法创建了文件列表 file GLOB RECURSE DEPLOY FILES PROJECT SOURCE DIR install 我想将所有这些文件安装在 usr myproject 但我想维护已安装文件夹上的文件树 ins
  • CMake 创建可执行文件时未定义的引用

    我是 CMake 新手 我正在尝试编译我的项目 该项目创建了一些静态库和一些可执行文件 下面是我拥有的文件结构的示例 PROJECT SRC 子项目 1该文件夹的 cpp 所有源文件 和CMakeLists txt 1 创建静态库 子项目
  • CMake“无法运行 MSBUILD.exe”命令错误

    当我想为 opencv 3 3 0 创建 Visual Studio 15 2017 make 文件时 它给了我以下错误消息 error in configuration process project files maybe invali
  • Clang 与 CLion:无法获取编译器信息

    我尝试通过更改在 CLion 中从 gcc 切换到 clang工具链偏爱 但现在 cmake 失败并显示以下内容 Cannot get compiler information Compiler exited with error code
  • Cygwin 下使用 CMake 编译库

    我一直在尝试使用 CMake 来编译 TinyXML 作为一种迷你项目 尝试学习 CMake 作为补充 我试图将其编译成动态库并自行安装 以便它可以工作 到目前为止 我已经设法编译和安装它 但它编译成 dll 和 dll a 让它工作的唯一
  • 将 dll/lib 链接到 cmake 项目

    我试图将库链接到我的 cmake 项目 但遇到链接器错误 我花了 2 个小时尝试解决这个问题 并创建了一个简单的项目 在其中对所有路径进行了硬编码 CMAKE MINIMUM REQUIRED VERSION 3 0 PROJECT Tes
  • CMake 找不到请求的 Boost 库

    既然我已经浏览了其他人的解决方案几个小时 但找不到适合我的问题的正确答案 我想将我的具体问题带给您 我正在尝试使用 CMake 构建 vsomeip 为此 我之前构建了 boost 1 55 但是 我在 CMake 中收到以下错误 The
  • 使用 CMake 在 iOS 中使用另一个 STATIC 库创建一个 STATIC 库

    我有一个 libfooi a 的集合 libfoo1 a libfoo2 a libfoo3 a 使用工厂 带有静态代码 有一个公共接口来创建 C 对象 使用 CMake 我选择其中之一 并创建一个链接它并添加所有内容的 libfooWra
  • 如何使用cmake查找库?

    要将可执行文件与驻留在标准位置的库链接 可以在 CmakeLists txt 文件中执行以下操作 create executable generate mesh generate mesh cpp target link libraries
  • 使用 CMake 和 clang 在 Windows 上构建简单的 C++ 项目

    我正在尝试在 Windows 10 上构建一个简单的 Hello World 程序 最好使用 CMake 和 clang 如果我使用 MinGW 的 g 编译器 我可以成功编译 链接和运行同一个项目 但当我尝试使用 clang 时会遇到问题

随机推荐

  • Macbook pro外接显卡实现深度学习

    耗时一整天加一晚上终于成功了安装配置外接GPU并运行深度学习案列 故事的缘由 2017年底鬼使神差的买了个macbook xff0c 放在家里吃了一年灰 xff0c 心想还是要用起来啊 目前主要从事数据挖掘机器学习的工作 xff0c 需要搞
  • Gradle技术之一 Groovy语法精讲

    Gradle技术之一 Groovy语法精讲 gradle脚本是基于groovy语言开发的 xff0c 想要学好gradle必须先要对groovy有一个基本的认识 1 Groovy特点 groovy是一种DSL语言 xff0c 所谓的DSL语
  • 字符串子串的查找

    1 考虑用标准函数库中 strstr 函数 包含文件 xff1a string h 函数名 strstr 函数原型 xff1a extern char strstr char str1 char str2 功能 xff1a 从字符串str1
  • 大锤老湿教您如何配置TP-Link路由器组建wifi上网

    TP Link路由器设置教程 大家好 xff0c 今天由大锤老湿教大家如何设置使用最广的TP Link路由器 一般家庭都希望能上wifi 那么首先看看我们如何将新买回的或者由于故障已经恢复成重置出厂状态的路由器 xff0c 如何经过重新设置
  • 【ESP01S】使用串口调试助手,发送AT指令收回的是乱码/重复一遍AT指令发回的问题

    调试帮助 span class token punctuation span 技术交流Q xff1a span class token number 1083091092 span xff08 备注CSDN xff09 一 问题描述 在使用
  • 刷leetcode使用python还是c++?

    我身边80 的程序员朋友在刷题的时候会选择Java xff0c 很少有人用C 43 43 来刷题 这两门语言各有特点 xff1a C 43 43 xff1a 从C语言发展过来的一门语言 xff0c 继承了灵活 xff08 可以潜入任何现代的
  • VINS-Mono代码精简版代码详解-后端非线性优化(三)

    非线性优化部分代码解析 之前已经对VINS Mono的初始化部分进行了介绍 xff0c 下面结合代码和公式介绍其非线性优化部分 本文部分参考 https blog csdn net u012871872 article details 78
  • Ubuntu IO占用过多导致文件读取变慢的原因查找方法

    问题描述 xff1a 多用户服务器 xff0c ubuntu系统 xff0c 突然点开文件夹 xff0c 发现变慢 查看方法 xff1a step1 xff1a 进入管理员用户 step2 xff1a 运行iostat x 1 在显示的结果
  • ROS Docker

    Docker 常用指令 docker pull osrf ros galactic desktop 从网络上下载镜像 docker images 查看已加载镜像列表 window docker界面 xff1a 命令行结果 xff1a doc
  • Win10C盘文件夹内容详解(持续更新,欢迎留言)

    本文参考以下博客 Roaming和Local的区别 C Users 用户名 AppData 1 Local和Roaming之间的区别 xff1a Local 比较大 xff0c 非漫游应用数据 Roaming 一般是漫游应用数据 2 Roa
  • STM32运行FreeRTOS

    使用ARM Keil 的 Keil uVision IDE xff0c 在 STM32上运行 FreeRTOS 内核 物料清单 软件 在创建新项目之前 xff0c 我们必须安装软件包 下面是打印屏幕 xff0c 其中包含如何执行此操作的步骤
  • ESP32实践FreeRTOS

    将部分代码作为应用程序中的任务独立执行可以简化大型复杂问题的设计 当有多个 CPU 时 xff0c 任务支持还允许选定的功能并行运行 本文将调查 Arduino 框架对 ESP32 系列设备的 FreeRTOS 任务支持 除了少数例外 xf
  • 黑马程序员—5—Java基础:多态学习笔记和学习心得体会

    lt ahref 61 34 http www itheima com 34 target 61 34 blank 34 gt android 培训 lt a gt lt ahref 61 34 http www itheima com 3
  • 图像去噪算法简介

    一 xff0c 背景 随着各种数字仪器和数码产品的普及 xff0c 图像和视频已成为人类活动中最常用的信息载体 xff0c 它们包含着物体的大量信息 xff0c 成为人们获取外界原始信息的主要途径 然而在图像的获取 传输和存贮过程中常常会受
  • Android 7 Nougat 源码目录结构

    code style margin 0px auto font family none padding 0px color inherit background color transparent art Android Runtime x
  • 【无人驾驶规划】BOSS无人车规划算法

    无人驾驶规划 BOSS无人车规划算法 1 boss运动规划结构2 轨迹生成2 1 状态约束2 2 车辆模型2 3 控制参数化2 4 初始化轨迹2 5 轨迹优化 3 on road模式规划3 1 路径生成3 2 轨迹生成3 3 轨迹速度配置3
  • 这也太全面了 阿里王牌级“Docker全线笔记”,Github已标星80k+,我太爱

    写在开头 司汤达说过 xff1a 一个人只要强烈地坚持不懈地追求 xff0c 他就能达到目的 Docker的创始人Solomon Hykes就是以这样的精神 xff0c 在docker即将坚持不下去的时候 xff0c 选择的不是放弃 xff
  • 如何在keil5中新建.c和.h文件?

    有两种方法 xff1a 方法1 在keil5内部添加两个文件分别为 c和 h文件 xff0c 可以保存在一个新建的文件夹里 xff08 前提是此文件夹是在keil5内部保存时新建的文件夹 xff0c 而不是在keil5软件外自己新建的文件夹
  • CMake(十二):构建类型

    本章和下一章涉及两个密切相关的主题 构建类型 在某些IDE工具中也称为构建配置或构建方案 是一种高级控件 xff0c 它选择不同的编译器和链接器行为集 构建类型的操作是本章的主题 xff0c 而下一章将介绍控制编译器和链接器选项的更具体细节
  • CMake:构建、链接静态库和动态库

    CMake 构建 链接静态库和动态库 导言一 多目录多文件CMake构建方式1 项目结构2 message h3 message cpp4 hello world cpp5 CMakeLists txt6 构建及编译 二 静态库和动态库简介