1使用cmake编译静态库

项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
│  CMakeLists.txt

├─out
│ └─build
│ └─x64-debug
├─ProjectA
│ CMaKeLists.txt
│ main.cpp

└─ProjectB_lib
CMakeLists.txt
ProjectB.cpp
ProjectB.h

根目录文件

CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cmake_minimum_required(VERSION 3.15)
project(MultiProjectSolution)

# --- 1. MSVC 特殊优化配置 ---
if (MSVC)
# 开启多核并行编译
add_compile_options(/MP)

# 智能调试信息格式策略 (CMP0141)
if (POLICY CMP0141)
cmake_policy(SET CMP0141 NEW)
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT
"$<IF:$<AND:$<C_COMPILER_ID:MSVC>,$<CXX_COMPILER_ID:MSVC>>,$<$<CONFIG:Debug,RelWithDebInfo>:EditAndContinue>,$<$<CONFIG:Debug,RelWithDebInfo>:ProgramDatabase>>")
endif()
endif()

# --- 2. 添加子项目 ---
# 注意顺序:先添加被依赖的库,再添加执行程序
add_subdirectory(ProjectB_lib)
add_subdirectory(ProjectA)

ProjectA

CMakeLists.txt

1
2
3
4
5
6
7
8
9
# 创建执行程序
add_executable(ProjectA main.cpp)

# 链接 ProjectB_lib
# 这一行会自动完成三件事:
# 1. 链接 ProjectB_lib.lib
# 2. 自动获得 ProjectB 的头文件搜索路径
# 3. 自动同步使用 C++20 标准
target_link_libraries(ProjectA PRIVATE ProjectB_lib)

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include "ProjectB.h" // CMake 已经把路径处理好了,直接引用即可

int main() {
// 1. 调用静态库中的函数
std::string version = ProjectB::getLibraryVersion();

// 2. 打印输出
ProjectB::printMessage("Hello from ProjectA calling ProjectB!");
std::cout << "Library Version: " << version << std::endl;

std::cout << "Press Enter to exit..." << std::endl;
std::cin.get();
return 0;
}

ProjectB_lib

CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
# 创建静态库
add_library(ProjectB_lib STATIC
ProjectB.cpp
ProjectB.h
)

# 设置 C++20 标准 (会自动传播给引用者)
target_compile_features(ProjectB_lib PUBLIC cxx_std_20)

# 关键:导出头文件路径,这样 ProjectA 才能 #include "ProjectB.h"
target_include_directories(ProjectB_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

ProjectB.h

1
2
3
4
5
6
7
8
9
10
#pragma once

#include <string>

class ProjectB {
public:
// 一个简单的逻辑函数
static std::string getLibraryVersion();
static void printMessage(const std::string& msg);
};

ProjectB.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "ProjectB.h"
#include <iostream>

// 如果使用的是 C++20,可以尝试包含 <format>
// #include <format>

std::string ProjectB::getLibraryVersion() {
return "v1.0.0 (Static Library)";
}

void ProjectB::printMessage(const std::string& msg) {
std::cout << "[ProjectB_lib] " << msg << std::endl;
}

编译

编译命令

1
cmake -S . -B build && cmake --build build --config Debug

编译脚本(windows下用msvc编译)

windows下git bash可执行

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
#!/bin/bash

# --- 配置区域 ---
BUILD_DIR="out/build"
CONFIG="Debug" # 可选 Debug 或 Release
GENERATOR="Visual Studio 17 2022" # 确保与安装的 VS 版本匹配

# 检查是否传入了 "clean" 参数
if [ "$1" == "clean" ]; then
echo "清理构建目录..."
rm -rf $BUILD_DIR
exit 0
fi

echo "--- 开始配置项目 (Configuration) ---"
# -S . (当前目录为源码路径)
# -B $BUILD_DIR (输出路径)
# -G (指定生成器)
cmake -S . -B $BUILD_DIR -G "$GENERATOR" -A x64

# 检查配置是否成功
if [ $? -ne 0 ]; then
echo "❌ CMake 配置失败,请检查 CMakeLists.txt"
exit 1
fi

echo "--- 开始编译项目 (Build) ---"
# --parallel 开启多核编译(根据 CPU 核心数自动分配)
cmake --build $BUILD_DIR --config $CONFIG --parallel

# 检查编译是否成功
if [ $? -eq 0 ]; then
echo "--------------------------------------"
echo "✅ 编译成功!"
echo "可执行文件位置: $BUILD_DIR/ProjectA/$CONFIG/ProjectA.exe"
echo "--------------------------------------"
else
echo "❌ 编译过程中出现错误"
exit 1
fi

编译脚本(兼容windows环境与linux环境)

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
#!/bin/bash

# 1. 自动检测系统,设置多核编译参数
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
# Windows 环境 (Git Bash)
PARALLEL_JOBS=$(nproc) # 或手动指定如 8
EXE_EXT=".exe"
else
# Linux / macOS 环境
PARALLEL_JOBS=$(nproc || sysctl -n hw.ncpu)
EXE_EXT=""
fi

BUILD_DIR="out/build"
CONFIG="Debug"

# 清理逻辑
if [ "$1" == "clean" ]; then
rm -rf $BUILD_DIR
echo "清理完成。"
exit 0
fi

echo "--- 正在自动配置项目 (Cross-platform) ---"
# 去掉 -G,让 CMake 自动探测
cmake -S . -B $BUILD_DIR -DCMAKE_BUILD_TYPE=$CONFIG

echo "--- 正在编译 (并行任务数: $PARALLEL_JOBS) ---"
# --parallel 会自动根据系统调用对应的加速指令
cmake --build $BUILD_DIR --config $CONFIG --parallel $PARALLEL_JOBS

if [ $? -eq 0 ]; then
echo "✅ 编译成功!"
echo "静态库位置: $BUILD_DIR/ProjectB_lib/"
else
echo "❌ 编译失败。"
exit 1
fi

只编译ProjectB_lib静态库命令

1
2
3
4
5
# 进入构建目录
cd out/build

# 只编译静态库目标 (ProjectB_lib 是在 CMakeLists 里 add_library 定义的名字)
cmake --build . --config Debug --target ProjectB_lib

2.使用cmake编译动态库

projectB_lib改成动态库需要改动哪些

从静态库(Static Library)切换到动态库(Dynamic Library/Shared Library)在 CMake 层面上非常简单,但在 C++ 代码层面(尤其是针对 Windows MSVC)需要处理“符号导出”的问题。

这是因为在 Windows 上,动态库里的函数和类默认是隐藏的,必须显式声明哪些内容要“导出”给 .dll 使用。


1. 修改 ProjectB_lib/CMakeLists.txt

只需要将 STATIC 改为 SHARED

1
2
3
4
5
6
7
8
9
10
11
# 将 STATIC 改为 SHARED
add_library(ProjectB_lib SHARED
ProjectB.cpp
ProjectB.h
)

target_compile_features(ProjectB_lib PUBLIC cxx_std_20)
target_include_directories(ProjectB_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

# 关键:告诉 CMake 在编译这个库时定义一个宏(用于导出符号)
target_compile_definitions(ProjectB_lib PRIVATE EXPORTING_PROJECTB)

2. 修改 ProjectB_lib/ProjectB.h (核心改动)

在 Windows 上,需要使用 __declspec(dllexport) 来导出符号,使用 __declspec(dllimport) 来导入符号。在 Linux 上则不需要(默认全部可见)。

为了保持跨平台兼容,通常会写一个宏开关:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#pragma once

#include <string>

// --- 跨平台导出/导入宏定义 ---
#if defined(_WIN32)
#ifdef EXPORTING_PROJECTB
#define PROJECTB_API __declspec(dllexport)
#else
#define PROJECTB_API __declspec(dllimport)
#endif
#else
#define PROJECTB_API __attribute__((visibility("default")))
#endif

// --- 在类名或函数前加上这个宏 ---
class PROJECTB_API ProjectB {
public:
static std::string getLibraryVersion();
static void printMessage(const std::string& msg);
};

3. 运行时的重大变化

切换到动态库后,会发现一个新问题:程序编译成功了,但运行报错“找不到 ProjectB_lib.dll”。

这是因为:

  • 静态库:编译时代码直接“塞进”了 ProjectA.exe,运行时不需要额外文件。
  • 动态库ProjectA.exe 在启动时会去寻找 .dll

解决方法(手动):

需要把生成的 ProjectB_lib.dll 拷贝到 ProjectA.exe 同级目录下。

解决方法(自动化 CMake):

在根目录 CMakeLists.txt 中加入这一行,让所有 DLL 自动生成在 EXE 同级目录:

1
2
3
4
# 将所有可执行文件、静态库、动态库输出到统一的 bin 目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)

4. 总结:改动清单

修改项 静态库 (Static) 动态库 (Shared)
CMake 类型 STATIC SHARED
Windows 符号 不需要特殊处理 必须使用 __declspec(dllexport)
生成文件 .lib (包含代码) .dll (代码) + .lib (导入库)
运行时 只有一个 .exe .exe 必须带着 .dll
编译速度 链接稍慢 链接极快(只需链接符号表)

💡 建议

对于内部模块(Monorepo),优先使用静态库,因为这样可以省去处理 __declspec 的麻烦,也不会有运行时找不到 DLL 的困扰。只有当库需要给第三方使用,或者库的体积巨大(几百 MB)需要减小 EXE 体积时,才考虑动态库。