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 仅为符号索引(导入库)。
相关推荐
努力实干11 小时前
面向对象设计原则——C++
开发语言·c++·数码相机
syagain_zsx14 小时前
jsoncpp 使用指南:编译链接、Json::Value 取值与高频坑
c++·json·库·http格式
布莱克60515 小时前
理解 = delete 与 = default:两种赋值语义的差异
c++
小小、码农16 小时前
[特殊字符]【C++11】类的新功能全面解析:移动构造、移动赋值、default、delete、final 与 override
linux·开发语言·网络·c++
库玛西17 小时前
外部排序全解析:败者树、置换-选择排序与最佳归并树
c语言·数据结构·c++·笔记·考研·算法
General_G18 小时前
创建型模式—原型(Prototype)模式
c++·设计模式·原型模式
syagain_zsx18 小时前
protobuf 实战:从 .proto 到 C++ 序列化的完整链路
c++·学习·程序人生·谷歌·protobuf
welfare96219 小时前
新号别搞:C++_list
开发语言·c++
xiaoye-duck19 小时前
《C++ 项目:手写大模型接入 SDK》云端 API 调用基础与 Apifox 调试实战
网络·c++·语言模型
坤坤子吖19 小时前
Linux 线程同步与互斥——条件变量与线程同步
linux·开发语言·c++·笔记·学习