CMake学习篇[2]---CMake进阶+非同级目录构建+静态库/动态库链接

文章目录

  • 前言
  • 1.多层目录
    • [1.1 项目结构](#1.1 项目结构)
    • [1.2 CMakeLists](#1.2 CMakeLists)
    • [1.3 命令说明](#1.3 命令说明)
      • [1.3.1 set](#1.3.1 set)
      • [1.3.2 include_directories](#1.3.2 include_directories)
      • [1.3.3 aux_source_directory](#1.3.3 aux_source_directory)
      • [1.3.4 flie](#1.3.4 flie)
    • [1.4 一些常用宏说明](#1.4 一些常用宏说明)
  • 2.创建自定义库与库链接
    • [2.1 生成静态库并链接](#2.1 生成静态库并链接)
      • [2.1.1 CMakeLists流程梳理](#2.1.1 CMakeLists流程梳理)
      • [2.1.2 关于CMake中的设置于VS中的对应关系](#2.1.2 关于CMake中的设置于VS中的对应关系)
    • [2.2 生成动态库并链接](#2.2 生成动态库并链接)
      • [2.2.1 CMakeLists流程梳理](#2.2.1 CMakeLists流程梳理)
      • [2.2.2 head.h文件修改](#2.2.2 head.h文件修改)
      • [2.2.3 关于CMake中的设置于VS中的对应关系](#2.2.3 关于CMake中的设置于VS中的对应关系)
      • [2.2.4 一些自己实现时遇到的坑](#2.2.4 一些自己实现时遇到的坑)
    • [2.3 命令说明](#2.3 命令说明)
      • [2.3.1 add_library](#2.3.1 add_library)
      • [2.3.2 link_libraries](#2.3.2 link_libraries)
  • 总结

前言

上一篇博客搭建了一个最最最基础的cmake入门demo,让大家对CMake的构建过程有了一个基本的认识,这篇博客更进一层,愈发贴近实战。

这篇博客主要讲我们的源文件包含多层目录如何找文件,如何生成静态库动态库并连接库的方式。这篇文章有点干巴,我自己写这篇博客之前,琢磨了好一阵子,总算是理顺了,下面且听我娓娓道来。

1.多层目录

1.1 项目结构

head.h

cpp 复制代码
#ifndef HEAD_H
#define HEAD_H

int SumInt(int a,int b); 
double SumDouble(double a,double b); 

#endif

myalgorithm.h

cpp 复制代码
#ifndef MYALGORITHM_H
#define MYALGORITHM_H

template<typename T>
T sum(T a,T b)
{
	return a+b;
} 

template<typename T>
T sub(T a,T b)
{
	return a-b;
} 


#endif

sumDouble.cpp

cpp 复制代码
#include<iostream>
#include"head.h" 

double SumDouble(double a,double b)
{
	return a+b;
}

sumInt.cpp

cpp 复制代码
#include<iostream>
#include"head.h" 

int SumInt(int a,int b)
{
	return a+b;
}

demo2.cpp

cpp 复制代码
#include<iostream>
#include"myalgorithm.h" 
#include"head.h" 

int main()
{
	int a=6,b=2;
	
	std::cout<<"sum(a,b)="<<sum(a,b)<<std::endl;
	std::cout<<"sub(a,b)="<<sub(a,b)<<std::endl;
	
	std::cout<<"sumInt(a,b)="<< SumInt(a,b)<<std::endl;
	std::cout<<"sumDouble(a,b)="<< SumInt(a,b)<<std::endl;
	return 0;
} 

上面的代码不用考虑太多,主要还是看CMakeLists的编写。

1.2 CMakeLists

cmake 复制代码
#CMake最小要求的版本
cmake_minimum_required(VERSION 3.11.0)

#工程名称
project(Demo2)

#增加-std=c++11 指定C++版本为C++11
set(CMAKE_CXX_STANDARD 11)

#将几个源文件用一个字符串存储
set(SRC_LIST
 ${CMAKE_CURRENT_SOURCE_DIR}/include/head.h
 ${CMAKE_CURRENT_SOURCE_DIR}/include/myalgorithm.h
 ${CMAKE_CURRENT_SOURCE_DIR}/src/sumDouble.cpp
 ${CMAKE_CURRENT_SOURCE_DIR}/src/sumInt.cpp
 ${CMAKE_CURRENT_SOURCE_DIR}/demo2.cpp)
 
#包含头文件
include_directories(${PROJECT_SOURCE_DIR}/include)

#设置输出路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/build_exe_dir)

#生成可执行项目
add_executable(demo2exe ${SRC_LIST})

CMakeLists流程梳理

我们且不论具体的命令的用法,先梳理一下大体的流程,并提及一些注意点。

首先设定好最小的CMake版本要求,工程名称,以及C++的标准(其实这里也没用上),再将我们的所需要的源文件全部用一个字符串存储,这样可以在后面复用这些源文件时,不用再全部输入一遍,直接用我们定义的字符串即可。再将头文件的查找路径包含进去,并设置输出路径,最后再生成我们需要的可执行文件的项目。

这里我们的SRC_LIST中包含了所需要的.h文件, 在这里包含的意思是指,我最终生成的项目中是否包含指定的.h文件,而如果不指定头文件查找路径,除了在自身同级目录,环境变量路径找,他是不会去.h文件实际存在的位置找的,所以还是需要使用include_directories进行包含

设置输出路径,是指我们运行时,可执行文件输出的位置在哪,一般根据配置不同,会自动生成Debug,Release等子目录,子目录中才会含有可执行文件.exe


在上面的set命令设置SRC_LIST的代码中,其实还有两种方式进行获取,通过aux_source_directoryflie命令。

使用代码如下:

cmake 复制代码
#将几个源文件用一个字符串存储
#方式一:通过手动set进行设置
set(SRC_LIST
 ${CMAKE_CURRENT_SOURCE_DIR}/include/head.h
 ${CMAKE_CURRENT_SOURCE_DIR}/include/myalgorithm.h
 ${CMAKE_CURRENT_SOURCE_DIR}/src/sumDouble.cpp
 ${CMAKE_CURRENT_SOURCE_DIR}/src/sumInt.cpp
 ${CMAKE_CURRENT_SOURCE_DIR}/demo2.cpp)
#方式二:通过搜索的方式获取,针对的是指定目录下所有的文件
# aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)
# aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SRC_LIST)

#方式三:通过搜索的方式获取,针对的是指定目录下,指定文件类型的文件,可加引号也可不加
#file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
#file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
#GLOB_RECURSE递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中。
#file(GLOB_RECURSE SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)

1.3 命令说明

第一篇中出现的命令这里不做赘述,这里只说明一下新出现的命令

1.3.1 set

在 CMake 中,set() 命令是最基础且常用的命令之一,主要用于设置变量的值,包括普通变量、缓存变量、环境变量等。它的功能非常灵活,是构建 CMake 脚本逻辑的核心工具之一。

set() 命令的基本格式如下:

cmake 复制代码
set(<变量名> <值>)

其中:
<变量名>:自定义的变量名称(通常使用大写字母,如 MY_VAR,遵循 CMake 约定)。
<值>:变量的取值,可以是字符串、路径、列表(多个值用空格分隔)等。

下面是一个常用法

cmake 复制代码
#单值变量
set(SOURCE_FILES main.cpp)  # 变量 SOURCE_FILES 被赋值为 "main.cpp"

#列表变量(多个值用空格分隔,本质是字符串,CMake 会按空格解析为列表)
set(SOURCE_FILES main.cpp utils.cpp)  # 列表形式,包含两个文件
#后续可通过 ${SOURCE_FILES} 引用变量值,例如
add_executable(myapp ${SOURCE_FILES})  # 等价于 add_executable(myapp main.cpp utils.cpp)

其他的也不多说了,只要记住,set第一个参数取名字,第二个参数给值,支持多个参数给到一个名字上

1.3.2 include_directories

在 CMake 中,include_directories() 是用于指定头文件搜索路径的命令,它告诉编译器在编译源代码时到哪些目录中查找 .h(或 .hpp 等)头文件。

基本用法

cmake 复制代码
include_directories([AFTER|BEFORE] [SYSTEM] <目录路径1> <目录路径2> ...)

<目录路径>:必填参数,指定一个或多个头文件所在的目录(可以是相对路径或绝对路径)。

可选参数:
AFTER/BEFORE:控制新路径在搜索路径列表中的位置(AFTER 追加到现有列表后,BEFORE 插入到现有列表前,默认是 AFTER)。
SYSTEM:将目录标记为 "系统目录",编译器会对该目录下的头文件减少警告(例如忽略一些语法检查警告)。

当源代码中使用 #include "xxx.h" 或 #include <xxx.h> 时,编译器会默认在系统标准路径(如 /usr/include、/usr/local/include 等)中查找头文件。如果头文件不在这些路径中(例如项目自己的 include 目录),就需要通过 include_directories() 告诉编译器额外的搜索路径,否则会出现 "头文件找不到" 的编译错误。

与 target_include_directories 的区别

CMake 3.0+ 推荐使用更现代的 target_include_directories() 替代 include_directories(),两者的核心区别在于:
include_directories() 是全局生效的,会将路径添加到当前 CMakeLists.txt 及所有子目录的目标(target,如可执行文件、库)的头文件搜索路径中。

target_include_directories() 是针对特定目标生效的,仅为指定的目标(如 myapp 或 mylib)添加头文件路径,更符合 "目标导向" 的现代 CMake 理念。

所以上面示例中的include_directories也可以用target_include_directories替代,只不过后者要指定项目,我这里由于是个demo所以用include_xxx更加省事。

为了更好的理解Windows在VS2017上开发CMakeLists命令和具体项目的配置关系,这里再贴个图说明一下include_directories的作用。

这里如果是用include,那就是解决方案下所有lib,exe,dll项目的包含目录中均含有你指定的路径,如果是实用工具,那可没有这个包含选项。

如果是target_include_xxx,那就是你指定的项目的包含目录,附加上你设定的路径

1.3.3 aux_source_directory

1.3.4 flie

1.4 一些常用宏说明

宏名称 含义 备注
CMAKE_CXX_STANDARD C++标准 11,14,17等等
CMAKE_CURRENT_SOURCE_DIR 当前处理的CMakeLists.txt所在的路径
CMAKE_CURRENT_BINARY_DIR target 编译目录
PROJECT_SOURCE_DIR 使用cmake命令后紧跟的目录,一般是工程的根目录
PROJECT_BINARY_DIR 执行cmake命令的目录
EXECUTABLE_OUTPUT_PATH 重新定义目标二进制可执行文件的存放位置
LIBRARY_OUTPUT_PATH 重新定义目标链接库文件的存放位置
PROJECT_NAME 返回通过PROJECT指令定义的项目名称
CMAKE_BINARY_DIR 项目实际构建路径,假设在build目录进行的构建,那么得到的就是这个目录的路径

2.创建自定义库与库链接

2.1 生成静态库并链接

先看一段CMakeLists的代码

cmake 复制代码
#CMake最小要求的版本
cmake_minimum_required(VERSION 3.11.0)

#工程名称
project(Demo3)

#增加-std=c++11 指定C++版本为C++11
set(CMAKE_CXX_STANDARD 11)

set(SRC_LIST
 ${CMAKE_CURRENT_SOURCE_DIR}/src/sumDouble.cpp
 ${CMAKE_CURRENT_SOURCE_DIR}/src/sumInt.cpp)

set(HEAD_LIST
 ${CMAKE_CURRENT_SOURCE_DIR}/include/head.h
 ${CMAKE_CURRENT_SOURCE_DIR}/include/myalgorithm.h)
 
#包含头文件
include_directories(${PROJECT_SOURCE_DIR}/include)

#设置输出路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/build_exe_dir)
#设置库的输出存放路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

#制作静态库
add_library(demo3static STATIC ${SRC_LIST} ${HEAD_LIST})

#链接静态库,先指定静态库的位置,以防查找不到
link_directories(${PROJECT_SOURCE_DIR}/lib)
link_libraries(demo3static)

add_executable(demo3exe  demo3.cpp)

2.1.1 CMakeLists流程梳理

相比于上一节,我们对于SRC_LIST进行了优化,将SRC_LIST中的.cpp.h文件进行了分隔,分别放到SRC_LISTHEAD_LIST

这里我们先设置好库的输出存放路径,接着使用add_library命令制作一个静态,同时由于我们手动设置了库的存放路径,所以这里需要再手动link一下库的目录,以防找不到库的情况,最后再链接库即可。

设置库输出路径,通过set命令进行设置,使用到了上面介绍的宏LIBRARY_OUTPUT_PATH,这里也会根据不同配置Debug/Release等自动生成子文件夹

这里制作静态库的命令add_library,平常使用就三个参数,自定义库名静态动态(STATIC/SHARED)源文件

倒数第二步链接库,这里有点学问,我们只需要指定库名即可,即使这个文件可能是demo3static.lib,后缀.lib我们不用管,只需要指定库名即可。

Linux中,静态库名字分为三部分:lib+库名字+.a,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。

最后效果大体如下:


2.1.2 关于CMake中的设置于VS中的对应关系

  • 关于添加连接库,实际上相当于就是VS里面,链接器中的附加依赖项
  • 我们创建自定义库的时候,库的名称以及输出位置的设定,相当于VS中下面的配置
  • 而我们设置的输出目录以及add_executeable命令里面指定的exe的名称,其实相当于VS这里的配置

2.2 生成动态库并链接

先看一段CMakeLists的代码

cmake 复制代码
#CMake最小要求的版本
cmake_minimum_required(VERSION 3.11.0)

#工程名称
project(Demo4)

#增加-std=c++11 指定C++版本为C++11
set(CMAKE_CXX_STANDARD 11)

set(SRC_LIST
 ${CMAKE_CURRENT_SOURCE_DIR}/src/sumDouble.cpp
 ${CMAKE_CURRENT_SOURCE_DIR}/src/sumInt.cpp)

set(HEAD_LIST
 ${CMAKE_CURRENT_SOURCE_DIR}/include/head.h
 ${CMAKE_CURRENT_SOURCE_DIR}/include/myalgorithm.h)

#包含头文件
include_directories(${PROJECT_SOURCE_DIR}/include)

#设置输出路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/build_exe_dir)
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/build_exe_dir)

#制作动态库
add_library(demo4shared SHARED ${SRC_LIST} ${HEAD_LIST} )
link_directories(LIBRARY_OUTPUT_PATH)
#如果不指定名称会自动生成"项目名_EXPORTS"的宏
target_compile_definitions(demo4shared PUBLIC MY_DEMO4SHARED_EXPORTS)

add_executable(demo4exe  demo4.cpp)

#链接动态库
target_link_libraries(demo4exe demo4shared)

2.2.1 CMakeLists流程梳理

这里和静态库的很像,基本保持一致,部分行有些不同

  • 23行中让库的输出目录和生成的可执行文件在同一目录下,这里先不说为什么后面再讲。
  • 26行add_library中,由静态库的STATIC改为了SHARED
  • 29行多了个编译定义命令target_complie_definitions,这条命令的意思是给demo4shared这个动态库定义了一个自定义宏,MY_DEMO4SHARED_EXPORTS,具体用法我们看代码

动态库和静态库其实还是有很大区别的,我不是指它们和内存的关系,我是指在使用上。

在Windows端,需要使用__declspec(dllexport)__declspec(dllimport)来说明是导出还是导入动态库dll,既然是生成动态库,所以我们需要对head.h做一个修改。

2.2.2 head.h文件修改

cpp 复制代码
#ifndef HEAD_H
#define HEAD_H

// 导出修饰符,用于动态库项目
#ifdef MY_DEMO4SHARED_EXPORTS
#define DEMO4SHARED_API __declspec(dllexport)
#else
#define DEMO4SHARED_API __declspec(dllimport)
#endif
DEMO4SHARED_API  int SumInt(int a,int b);
DEMO4SHARED_API  double SumDouble(double a,double b);

#endif

这里我们多了一个宏的判断操作,用来判断head.h文件中的函数是否需要导出到库。这里的宏MY_DEMO4SHARED_EXPORTS,必须得和cmake中的target_complie_definitions定义的宏一致,因为我们用的就是cmake的宏是否存在来判定导出还是导入到动态库。

改完head.h后,构建编译通过,应该就可以正常运行。

2.2.3 关于CMake中的设置于VS中的对应关系

  • 我们添加的宏,实际上就是动态库的预处理器定义

2.2.4 一些自己实现时遇到的坑

  1. 生成的可执行文件必须和应用的外部dll库放在一起吗?不能通过link_directories找到后直接调用吗?能否把动态库的位置和可执行文件的位置分开?

生成的可执行文件不必须和外部 DLL 放在一起,但link_directories只能解决编译链接阶段的 "找库" 问题,无法解决运行阶段的 "找 DLL" 问题 ------ 这两个阶段的 "找库逻辑" 完全不同,是 Windows 系统的底层机制决定的,需要明确区分。

一、先理清两个核心阶段的区别

Windows 程序的构建和运行分为两个独立阶段,"找库" 的逻辑和路径完全不同:

二、为什么link_directories无法解决运行阶段找 DLL 的问题?

link_directories的作用是告诉编译器 / 链接器:在编译链接时,去哪里找 "导入库(.lib)"(导入库是链接阶段的 "桥梁",仅包含 DLL 的符号信息,不包含实际代码)。

但运行阶段时,操作系统(Windows)加载 DLL 的逻辑是独立于 CMake 的link_directories配置的 ------Windows 会按照固定的优先级顺序搜索 DLL,而link_directories指定的路径不在这个搜索列表里,所以即使link_directories能找到导入库,运行时仍可能找不到 DLL。

三、Windows 运行时搜索 DLL 的优先级顺序(关键)

可执行文件运行时,Windows 会按以下顺序查找需要的 DLL(优先级从高到低):

可执行文件所在目录(这就是 "把 DLL 和 exe 放一起能运行" 的原因);

当前工作目录(即运行 exe 时的命令行所在目录,不是 exe 的目录);

系统目录(C:\Windows\System32,32 位 DLL;C:\Windows\SysWOW64,64 位 DLL);

Windows 目录(C:\Windows);

环境变量PATH中指定的目录(这是 "不把 DLL 和 exe 放一起" 的核心方案)。

四、不把 DLL 和 exe 放一起的 3 种可行方案

如果不想将 DLL 和可执行文件放在同一目录,可以通过以下方式让 Windows 在运行时找到 DLL:

方案 1:将 DLL 路径添加到系统环境变量PATH

这是最通用的方案,适合多个程序共用同一个 DLL 的场景:

找到你的 DLL 所在目录(例如${PROJECT_SOURCE_DIR}/lib/Debug);

右键 "此电脑"→"属性"→"高级系统设置"→"环境变量";

在 "系统变量" 中找到PATH,点击 "编辑"→"新建",粘贴 DLL 目录路径;

重启命令行 / IDE(环境变量修改后需要重启生效),再运行 exe。

注意:如果是 Debug 和 Release 模式的 DLL,需要分别将lib/Debug和lib/Release添加到PATH,或根据当前使用的模式切换PATH。

方案 2:通过 CMake 自动将 DLL 复制到PATH目录(适合开发阶段)

如果不想手动修改环境变量,可以在 CMake 中添加逻辑,将生成的 DLL 自动复制到系统PATH包含的目录(例如C:\Windows\System32,但不推荐修改系统目录,更安全的是复制到用户目录下的bin目录并添加到PATH):

cmake 复制代码
# 示例:将DLL复制到用户目录的 bin 目录(需先将该目录添加到 PATH)
set(USER_BIN_DIR $ENV{USERPROFILE}/bin)
file(MAKE_DIRECTORY ${USER_BIN_DIR})  # 确保目录存在

# 编译动态库后,自动复制DLL到 USER_BIN_DIR
add_custom_command(TARGET demo2shared POST_BUILD
  COMMAND ${CMAKE_COMMAND} -E copy
  $<TARGET_FILE:demo2shared>  # 动态库文件路径(自动包含 Debug/Release)
  ${USER_BIN_DIR}/$<TARGET_FILE_NAME:demo2shared>
  COMMENT "Copy DLL to user bin directory: ${USER_BIN_DIR}"
)

方案 3:在代码中指定 DLL 加载路径(进阶,适合特定场景)

如果是 C/C++ 代码,可以通过LoadLibraryA函数手动指定 DLL 的完整路径加载,绕过 Windows 的默认搜索规则(适合需要灵活控制 DLL 路径的场景):

cpp 复制代码
#include <windows.h>
#include <iostream>

// 定义DLL中的函数指针类型
typedef int (*SumIntFunc)(int, int);

int main() {
    // 手动指定DLL的完整路径(例如放在 D:\my_dlls 目录)
    HMODULE hDll = LoadLibraryA("D:\\my_dlls\\demo2shared.dll");
    if (hDll == NULL) {
        std::cout << "找不到DLL!错误码:" << GetLastError() << std::endl;
        return 1;
    }

    // 获取DLL中的函数地址
    SumIntFunc sumInt = (SumIntFunc)GetProcAddress(hDll, "SumInt");
    if (sumInt == NULL) {
        std::cout << "找不到函数!错误码:" << GetLastError() << std::endl;
        FreeLibrary(hDll);
        return 1;
    }

    // 调用DLL中的函数
    std::cout << sumInt(3, 5) << std::endl;

    // 释放DLL
    FreeLibrary(hDll);
    return 0;
}

注意:这种方式需要用 "显式加载"(LoadLibrary)替代 "隐式加载"(编译时链接导入库),适合对 DLL 路径有特殊要求的场景(如插件化架构)。

而我在cmake中将他们放到一起的方式,其实是避免了找不到的情况,当然了,我们还可以用自定义命令来将生成在别的地方的lib库拷贝一份到exe文件处,这样也不会出现找不到问题。

cmake 复制代码
add_custom_command(TARGET demo4exe POST_BUILD
                   COMMAND ${CMAKE_COMMAND} -E copy_directory
                   ${LIBRARY_OUTPUT_PATH}/$<IF:$<CONFIG:Debug>,Debug,Release> $<TARGET_FILE_DIR:demo4exe>
                   COMMENT "Copying dynamic library to executable directory"
                   VERBATIM)

  1. 为什么只改head.h,而myalgorithm.h不用修改,它们不都是头文件吗?为什么后者不需要dllexport?

myalgorithm.h 文件是纯模板函数(sum、sub)的实现,这类文件不需要添加 MYDLL_EXPORTS 相关的导出 / 导入逻辑,原因如下:

  1. 模板函数的特殊性:必须在头文件中实现

    C++ 模板的实现机制决定了模板函数 / 类的定义(实现)必须放在头文件中(或通过#include包含实现文件),因为编译器需要在编译时根据具体的模板参数(如 int、double)实例化出具体的函数代码。

    这意味着:

    模板函数不会像普通函数那样编译成 DLL 中的二进制代码(因为编译时还不知道具体实例化类型)。

    其他项目使用你的模板函数时,需要直接包含头文件,由该项目的编译器根据实际使用的类型(如 sum(1,2))实例化代码,而不是从 DLL 中 "导入" 已编译的符号。

  2. 为什么不需要 dllexport/dllimport?
    __declspec(dllexport) __declspec(dllimport)的作用是控制 DLL 中二进制符号的导出和导入,但模板函数:
    没有 "预编译的二进制符号"(因为依赖具体实例化类型),无法被 DLL 导出。

    其他项目使用时也不需要 "导入",而是直接通过头文件实例化,因此无需 dllimport。

    如果强行在模板函数中添加 MYDLL_API(即 dllexport/dllimport),反而会导致编译错误或无意义的冗余代码。


2.3 命令说明

2.3.1 add_library

在 CMake 中,add_library 是用于创建库文件(静态库、动态库或模块库)的核心命令。它的主要作用是将指定的源文件编译为库,供其他目标(如可执行文件或其他库)链接使用。

cmake 复制代码
add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] [source1] [source2 ...])

关键参数说明
<name>库的名称,CMake 会根据平台自动生成对应的库文件名(例如:在 Linux 上静态库为 lib<name>.a,动态库为 lib<name>.so;在 Windows 上静态库为 <name>.lib,动态库为 <name>.dll)。

库类型(可选,默认根据政策判断)
STATIC:生成静态库(.a/.lib),编译时会被直接链接到目标文件中。
SHARED:生成动态库(.so/.dll),运行时动态加载,可被多个程序共享。
MODULE:生成模块库(通常用于动态加载插件),不会被链接到其他目标,而是通过 dlopen 等机制加载。

如果不指定类型,CMake 会根据 BUILD_SHARED_LIBS 变量判断:若该变量为 ON,默认生成 SHARED 库,否则生成 STATIC 库。
EXCLUDE_FROM_ALL(可选)表示该库不会被默认构建(即执行 make 或 cmake --build 时不会自动编译),需显式指定目标(如 make <name>)才会构建。

源文件列表指定用于编译库的源文件(.c/.cpp 等),也可后续通过 target_sources 补充添加

注意

库名称需唯一,不能与其他目标(如可执行文件)重名。

动态库在不同平台可能需要特殊处理(如 Windows 需导出符号,可通过 __declspec(dllexport) 实现)。

若库被多个目标依赖,CMake 会自动处理链接关系,无需重复编译

在 CMake 中,link_libraries 是用于指定全局链接依赖的命令,它会为后续定义的所有目标(如可执行文件 add_executable 或库 add_library)添加链接依赖项。简单来说,就是 "提前声明" 哪些库需要被之后的目标默认链接。

cmake 复制代码
link_libraries([item1] [item2 ...])

其中 item 可以是:

已通过 add_library 定义的库目标名称(推荐);

系统库名称(如 pthread、m);

库文件的路径(不推荐,可移植性差)。

核心作用

link_libraries 是 "全局生效"的命令:它会为在它之后出现的所有目标 (add_executable 或 add_library)自动添加链接依赖。例如:

cmake 复制代码
# 声明后续目标都需要链接 math_lib 和 pthread
link_libraries(math_lib pthread)

# 生成可执行文件 app,会自动链接 math_lib 和 pthread
add_executable(app main.cpp)

# 生成库 utils,也会自动链接 math_lib 和 pthread
add_library(utils STATIC utils.cpp)

与 target_link_libraries 的区别

link_libraries 和 target_link_libraries 都用于链接库,但核心区别在于作用范围:

link_libraries:全局生效,影响之后所有目标("批量设置");

target_link_libraries:只针对指定目标生效("精准设置",推荐使用)。

常用场景与注意事项

  1. 不推荐优先使

    由于 link_libraries 是全局生效,项目复杂时容易导致 "不必要的链接"(比如某个目标其实不需要该库,但被强制链接),增加构建冗余或冲突风险。因此,更推荐使用 target_link_libraries 为具体目标精准链接依赖。

  2. 支持链接选项可以添加链接选项(如 -lpthread 或 PUBLIC/PRIVATE 等关键字,与 target_link_libraries 类似):

cmake 复制代码
# 后续目标链接 math_lib 时,将其依赖传递给依赖该目标的其他目标(PUBLIC 语义)
link_libraries(PUBLIC math_lib)
  1. 多次调用的叠加效果多次调用 link_libraries 会累积依赖项。例如:
cmake 复制代码
link_libraries(lib1)
link_libraries(lib2)
# 后续目标会同时链接 lib1 和 lib2
  1. 谨慎使用系统库路径尽量避免直接写库文件路径(如 link_libraries(/usr/lib/libxxx.so)),可移植性差。优先使用库目标名称或系统库名(如 pthread)。

link_libraries 适合简单项目中批量设置链接依赖,但在复杂项目中,更推荐用 target_link_libraries 为每个目标单独指定依赖,以提高清晰度和可维护性。实际开发中,target_link_libraries 是更主流的选择。


总结

这篇博客花了一天时间去验证实现+编写,和前年些W25Q128芯片精读一样,一杯茶配上键鼠,电脑前坐一天。刚好台风桦加沙来了,今天也停工,那就沉淀沉淀吧。

其他知识相关的内容,我只能说,无需多言,尽在文字中。相关代码和文件我同步上传至gitee,需要自取
https://gitee.com/Edwinwzy1/cmake-learn

相关推荐
起个名字费劲死了3 小时前
Pytorch Yolov11 OBB 旋转框检测+window部署+推理封装 留贴记录
c++·人工智能·pytorch·python·深度学习·yolo·机器人
高山有多高4 小时前
从 0 到 1 保姆级实现C语言双向链表
c语言·开发语言·数据结构·c++·算法·visual studio
aluluka4 小时前
Emacs 折腾日记(三十)——打造C++ IDE 续
c++·ide·emacs
半桔4 小时前
【网络编程】UDP 编程实战:从套接字到聊天室多场景项目构建
linux·网络·c++·网络协议·udp
Lucis__4 小时前
C++相关概念与语法基础——C基础上的改进与优化
c语言·开发语言·c++
草莓熊Lotso4 小时前
《算法闯关指南:优选算法--滑动窗口》--14找到字符串中所有字母异位词
java·linux·开发语言·c++·算法·java-ee
奔跑吧邓邓子5 小时前
【C++实战㊳】C++单例模式:从理论到实战的深度剖析
c++·单例模式·实战
SuperCandyXu5 小时前
P2168 [NOI2015] 荷马史诗-提高+/省选-
数据结构·c++·算法·洛谷