CMake制作动态库与静态库对比

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


一、静态库无需导出符号的核心原因

底层原理

  1. 静态库本质
    静态库是编译后的目标文件(.obj)的打包归档,它本身不是可独立运行的程序。

  2. 链接机制
    生成可执行文件(.exe)时,链接器会将静态库中被用到的代码/数据直接复制到最终的 .exe 内部 ,属于静态链接

  3. 符号可见性
    Windows 平台下,静态库的全局函数、变量默认全部公开可见 ,链接器可以直接解析所有符号,不需要 __declspec(dllexport) 这类导出修饰符

  4. 关键区分:两种 .lib 文件
    VS 中会出现两种后缀都是 .lib 的文件,作用完全不同:

    库类型 文件说明 链接阶段行为 运行时依赖
    静态库 纯代码归档文件 代码复制进 .exe 无额外依赖
    动态库配套 导入库(索引文件) 仅记录符号地址,不复制代码 必须依赖 .dll

二、动态库(DLL):头文件不写导出符号不行

核心规则

Windows 平台的 DLL(动态链接库) 有严格的符号隔离机制:
默认情况下,DLL 内的所有全局符号都是隐藏的,不会自动暴露给外部程序

如果不做显式导出声明,会直接导致:

  1. DLL 编译成功,但不会生成导出表
  2. 外部程序链接时,报 无法解析的外部符号 错误;
  3. 无法调用 DLL 中的函数。

补充对比:Linux/macOS 的动态库(.so/.dylib)默认导出所有符号,无需手动声明;但 Windows 为了性能和安全性,强制要求显式导出,这是平台特性差异。

动态库导出符号的两种标准方案

方案1:使用 __declspec 修饰符(VS 最常用、推荐)

配合宏封装,实现编译DLL时导出、调用DLL时导入的自动切换,这是工业级标准写法。

方案2:使用模块定义文件(.def

无需修改代码,通过配置文件声明导出符号,适合兼容老项目。


三、完整示例:基于加法函数的动态库实现

基于你之前的代码,改造为Windows 动态库标准格式,适配 VS + CMake:

1. 目录结构

复制代码
cmake_demo/
├── include/
│   └── lib.h      # 带导出宏的头文件
├── src/
│   ├── lib.cpp    # 动态库实现
│   └── main.cpp   # 调用方程序
└── CMakeLists.txt

2. 核心代码修改

include/lib.h(带导出/导入宏封装)

为了兼容 C++ 名字粉碎问题,添加 extern "C";通过宏区分编译库使用库的场景:

c 复制代码
#ifndef LIB_H
#define LIB_H

// 宏定义:编译DLL时,MYLIB_EXPORT 等价于 __declspec(dllexport)
// 调用方使用时,MYLIB_EXPORT 等价于 __declspec(dllimport)
#ifdef _WIN32
    #ifdef MYLIB_SHARED_EXPORTS
        #define MYLIB_API __declspec(dllexport)
    #else
        #define MYLIB_API __declspec(dllimport)
    #endif
#else
    // 非Windows平台,无需导出修饰符
    #define MYLIB_API
#endif

// 兼容C调用,防止C++名字改编
extern "C" {
    // 修饰函数:标记为导出/导入符号
    MYLIB_API int add(int a, int b);
}

#endif // LIB_H

src/lib.cpp(实现文件,无需修改修饰符)

c 复制代码
#include "lib.h"

// 实现函数,无需额外修饰
int add(int a, int b) {
    return a + b;
}

src/main.cpp(调用方代码,无修改)

c 复制代码
#include <stdio.h>
#include "lib.h"

int main() {
    int res = add(10, 20);
    printf("10 + 20 = %d\n", res);
    return 0;
}

3. CMakeLists.txt(编译动态库)

关键:用 SHARED 声明动态库,CMake 会自动定义 MYLIB_SHARED_EXPORTS 宏:

cmake 复制代码
cmake_minimum_required(VERSION 3.15)
project(AddDllDemo)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 核心:SHARED 表示编译为 Windows 动态库(DLL)
add_library(mylib SHARED src/lib.cpp)
target_include_directories(mylib PUBLIC include)

# 生成可执行文件
add_executable(main_app src/main.cpp)
# 链接动态库
target_link_libraries(main_app PRIVATE mylib)

4. 编译与运行(VS 环境)

  1. 用 CMake 生成 VS 解决方案,编译工程;
  2. 输出目录会生成两个关键文件:mylib.dll(运行时库)、mylib.lib(导入库);
  3. 直接运行 main_app.exe,可正常调用 DLL 中的 add 函数。

5. 测试「不写导出符号」的失败场景

如果删除头文件中的 MYLIB_API 修饰符,直接声明函数:

c 复制代码
// 错误写法:无导出修饰符
extern "C" int add(int a, int b);

编译后:

  • DLL 可正常生成,但无导出符号
  • 链接 main_app 时,VS 直接报错:无法解析的外部符号 add,程序无法运行。

补充方案:使用 .def 文件导出符号

如果不想修改代码,可以新建 mylib.def 文件,手动声明导出符号:

def 复制代码
LIBRARY mylib
EXPORTS
    add @1

在 CMake 中添加配置:

cmake 复制代码
target_sources(mylib PRIVATE mylib.def)

此时代码中无需任何导出修饰符 ,链接器会根据 .def 文件导出符号,同样可以正常调用。


四、关键补充说明

1. 关于 extern "C" 的作用

C++ 编译器会对函数名做名字粉碎(Name Mangling) ,导致外部调用时符号不匹配;

添加 extern "C" 会强制使用 C 语言的符号命名规则,保证跨调用约定兼容。

2. 动态库运行依赖

静态库编译后,.exe 可独立运行;

动态库编译后,必须将 xxx.dll 放在 .exe 同级目录,否则运行时会报错「找不到依赖库」。

3. CMake 自动宏的特性

当你用 add_library(xxx SHARED ...) 时,CMake 会自动生成 XXX_EXPORTS ,这也是我们封装 MYLIB_API 的依据,无需手动定义。


总结

  1. 静态库(.lib :无需任何导出符号,全局符号默认公开,链接时代码直接嵌入 .exe,这是你能直接调用函数的原因;
  2. Windows 动态库(.dll必须显式导出符号 ,不写导出修饰符会导致链接失败,推荐用 __declspec(dllexport/dllimport) + 宏封装的标准写法;
  3. 两种 .lib 文件切勿混淆:静态库是代码归档,动态库配套的 .lib 仅为符号索引(导入库)。
相关推荐
念恒1230638 分钟前
继承(下) (Inheritance)
c++
H Journey2 小时前
C++之 CMake、CMakeLists.txt、Makefile
开发语言·c++·makefile·cmake
研究点啥好呢6 小时前
Github热门项目推荐 | 创建你的像素风格!
c++·python·node.js·github·开源软件
_dindong6 小时前
cf1091div2 C.Grid Covering(数论)
c++·算法
沫璃染墨6 小时前
C++ string 从入门到精通:构造、迭代器、容量接口全解析
c语言·开发语言·c++
6Hzlia7 小时前
【Hot 100 刷题计划】 LeetCode 17. 电话号码的字母组合 | C++ 回溯算法经典模板
c++·算法·leetcode
计算机安禾7 小时前
【数据结构与算法】第36篇:排序大总结:稳定性、时间复杂度与适用场景
c语言·数据结构·c++·算法·链表·线性回归·visual studio
unicrom_深圳市由你创科技7 小时前
做虚拟示波器这种实时波形显示的上位机,用什么语言?
c++·python·c#
无限进步_7 小时前
【C++】电话号码的字母组合:从有限处理到通用解法
开发语言·c++·ide·windows·git·github·visual studio
C++ 老炮儿的技术栈8 小时前
GCC编译时无法向/tmp 目录写入临时汇编文件,因为设备空间不足,解决
linux·运维·开发语言·汇编·c++·git·qt