CMake 笔记 | [3] 静态库和动态的补充

警告
本文最后更新于 2024-01-14,文中内容可能已过时。

一、导言

对CMake在不同平台上构建动态库和静态库做进一步的探索,即如何利用一个比较统一的模板在不同的平台构建静态库和动态库,以及对add_library命令的其他参数做进一步的探索。

二、库模板

2.1 项目结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
.
├── cmake
│  └── message_config.cmake.in
├── message-module
│   ├── include
│   │  ├── message_export_lib.h
│   │  └── message.h
│   ├── src
│   │   └── message.cpp
│   └── CMakeLists.txt
├── hello_world.cpp
└── CMakeLists.txt

本项目的结构相对比较复杂,为了能够生成一套比较标准的库(静态库/动态库),所以项目中会包含很多配置项,接下来我们会对项目中的所有内容进行一一讲解。

2.2

根目录下的CMakeLists.txt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)

project(hello-world LANGUAGES CXX)

set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

option(BUILD_SHARED_LIBS "Specifies the type of libraries (SHARED or STATIC) to build" OFF)

# Set install direcotory
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
  set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/output/)
endif()

add_subdirectory(${CMAKE_SOURCE_DIR}/message-module)

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

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

target_link_libraries(
    ${PROJECT_NAME}
    test_message
)
Tip
1
option(BUILD_SHARED_LIBS "Specifies the type of libraries (SHARED or STATIC) to build" OFF)

首先,我们提供了在执行cmake命令时的参数选项,默认默认情况下BUILD_SHARED_LIBS的状态是关闭的(OFF),即默认情况下我们默认构建的是静态库。

当我们要构建动态库时,我们需执行以下命令(假设你已经在项目中构建了build文件夹,并且你现在在build目录中)

1
cmake .. -DBUILD_SHARED_LIBS=ON
Tip
1
2
3
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
  set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/output/)
endif()

如果我们没有指定在make install后的输出路径,则CMAKE_INSTALL_PREFIX 将设置为${CMAKE_SOURCE_DIR}/output/,即当前项目下的output目录。

2.3 cmake目录下的message_config.cmake.in

1
2
3
@PACKAGE_INIT@

include ( "${CMAKE_CURRENT_LIST_DIR}/message_config.cmake" )

为了能够生成一个标准的库,我们写了一个message_config.cmake.in文件,该文件执行make install时将被调用,调用时我们再进行讲解。

2.4 message-module下message_export_lib.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#ifndef MESSAGE_EXPORT_LIB_H_
#define MESSAGE_EXPORT_LIB_H_

#ifdef MESSAGE_LIB_SHARED_BUILD
    #ifdef _WIN32
        #ifdef MESSAGE_LIB_EXPORTS
            #define MESSAGE_LIB_API __declspec(dllexport)
        #else
            #define MESSAGE_LIB_API __declspec(dllimport)
        #endif  // MESSAGE_LIB_EXPORTS
    #else
        #define MESSAGE_LIB_API
    #endif  // _WIN32
#else
    #define MESSAGE_LIB_API
#endif  // MESSAGE_LIB_SHARED_BUILD

#endif // ! MESSAGE_EXPORT_LIB_H_

首先,如果我们定义了宏定义MESSAGE_LIB_SHARED_BUILD(即我们要构建动态库时),我们将执行以下命令:如果是Windows平台,并且是在生成动态库时,即定义了(MESSAGE_LIB_EXPORTS),将__declspec(dllexport)定义为MESSAGE_LIB_API;如果是使用动态库时,即未定义(MESSAGE_LIB_EXPORTS),则将__declspec(dllimport)定义为MESSAGE_LIB_API。如果是非Windows平台,则对宏MESSAGE_LIB_API不做任何操作(因为在非Windows平台上可以动态库的生成和使用与静态库是一样的)。

然后,如果我们未定义宏定义MESSAGE_LIB_SHARED_BUILD(即我们要构建静态库时),我们对宏MESSAGE_LIB_API不做任何操作。

注意:关于宏定义MESSAGE_LIB_SHARED_BUILD和MESSAGE_LIB_EXPORTS是否要添加利用message-module下的CMakeLists.txt进行配置。

2.5 message-module下message.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#ifndef MESSAGE_HEADER_H_
#define MESSAGE_HEADER_H_

#include <iostream>
#include <string>

#include "message_export_lib.h"
class MESSAGE_LIB_API Message {
 public:
  Message() {}

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

我们将头文件message_export_lib.h包含进来,来控制在不同平台生成不同的库时的选项。

2.6 message-module下的CMakeLists.txt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
file(GLOB SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)

if (BUILD_SHARED_LIBS)
    add_library(test_message SHARED ${SOURCE_FILE})
    target_compile_definitions(test_message PUBLIC -DMESSAGE_LIB_SHARED_BUILD)
    target_compile_definitions(test_message PRIVATE -DMESSAGE_LIB_EXPORTS)
else()
    add_library(test_message STATIC ${SOURCE_FILE})
endif()

# 添加别名,以便库可以在构建树中使用,例如在测试时
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"
)

install(EXPORT message_export_target
        FILE message_lib.cmake
        DESTINATION lib/cmake/test_message
)


include(CMakePackageConfigHelpers)
# generate the config file that is includes the exports
configure_package_config_file(
    ${CMAKE_SOURCE_DIR}/cmake/message_config.cmake.in
    "${CMAKE_SOURCE_DIR}/cmake/message_config.cmake"
    INSTALL_DESTINATION "lib/cmake/test_message"
)

# generate the version file for the config file
write_basic_package_version_file(
    "${CMAKE_SOURCE_DIR}/cmake/message_config_version.cmake"
    VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}"
    COMPATIBILITY AnyNewerVersion
)

install(FILES
    ${CMAKE_SOURCE_DIR}/cmake/message_config.cmake
    ${CMAKE_SOURCE_DIR}/cmake/message_config_version.cmake
    DESTINATION lib/cmake/test_message
)

export(EXPORT message_export_target FILE ${CMAKE_SOURCE_DIR}/cmake/message_config_version.cmake)

如果我们让宏BUILD_SHARED_LIBS为开启状态(即我们要构建动态库),我们要将宏定义MESSAGE_LIB_SHARED_BUILDMESSAGE_LIB_EXPORTS添加到编译器中,这样我们的头文件message_export_lib.h中便知道我们要构建动态库

否则(即我们没有让BUILD_SHARED_LIBS开启),我们将构建静态库

利用CMakePackageConfigHelpers模块生成关于test_message库的cmake的配置文件,且可以使用命令find_package命令找到库test_message库。关于以上命令的具体参数使用,我们将在具体的命令学习章节进行具体的讲解和学习。

三、add_library其他参数的一些探索

3.1 生成对象库

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)

project(recipe-03 LANGUAGES CXX)

add_library(message-objs
    OBJECT
        Message.hpp
        Message.cpp
    )
# this is only needed for older compilers
# but doesn't hurt either to have it
set_target_properties(message-objs
    PROPERTIES
        POSITION_INDEPENDENT_CODE 1
    )

add_library(message-shared
    SHARED
        $<TARGET_OBJECTS:message-objs>
    )

add_library(message-static
    STATIC
        $<TARGET_OBJECTS:message-objs>
    )

add_executable(hello-world hello-world.cpp)

target_link_libraries(hello-world message-static)

为了保证编译的目标文件与生成位置无关,可以通过使用set_target_properties命令,设置message-objs目标的相应属性来实现。

**注意:**可能在某些平台和/或使用较老的编译器上,需要显式地为目标设置POSITION_INDEPENDENT_CODE属性。

现在,可以使用这个对象库来获取静态库(message-static)和动态库(message-shared)。要注意引用对象库的生成器表达式语法:$<TARGET_OBJECTS:message-objs>。生成器表达式是CMake在生成时(即配置之后)构造,用于生成特定于配置的构建输出。

3.2 将静态库和动态库同时命名为同名的两个库

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17

add_library(message-shared
  SHARED
    $<TARGET_OBJECTS:message-objs>
    )
set_target_properties(message-shared
    PROPERTIES
        OUTPUT_NAME "message"
    )
add_library(message-static
    STATIC
        $<TARGET_OBJECTS:message-objs>
    )
set_target_properties(message-static
    PROPERTIES
        OUTPUT_NAME "message"
    )
Buy me a coffee~
支付宝
微信
0%