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)
if (MSVC) add_compile_options(/MP) 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()
add_subdirectory(ProjectB_lib) add_subdirectory(ProjectA)
|
ProjectA
CMakeLists.txt
1 2 3 4 5 6 7 8 9
| add_executable(ProjectA main.cpp)
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"
int main() { std::string version = ProjectB::getLibraryVersion(); 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 )
target_compile_features(ProjectB_lib PUBLIC cxx_std_20)
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>
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" GENERATOR="Visual Studio 17 2022"
if [ "$1" == "clean" ]; then echo "清理构建目录..." rm -rf $BUILD_DIR exit 0 fi
echo "--- 开始配置项目 (Configuration) ---"
cmake -S . -B $BUILD_DIR -G "$GENERATOR" -A x64
if [ $? -ne 0 ]; then echo "❌ CMake 配置失败,请检查 CMakeLists.txt" exit 1 fi
echo "--- 开始编译项目 (Build) ---"
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
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then PARALLEL_JOBS=$(nproc) EXE_EXT=".exe" else 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) ---"
cmake -S . -B $BUILD_DIR -DCMAKE_BUILD_TYPE=$CONFIG
echo "--- 正在编译 (并行任务数: $PARALLEL_JOBS) ---"
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
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
| 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})
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
| 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 体积时,才考虑动态库。