Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← C++ 编程 / C++ Programming

插件系统 / Plugin Systems

1. 插件工程:可演进的模块边界 / Plugin Engineering and Evolvable Module Boundaries

2. 运行时动态加载:LoadLibrary 与 dlopen —— 打开插件的大门 / Runtime Dynamic Loading with LoadLibrary and Dlopen

3. 插件接口设计:C ABI、函数表与受控 C++ 接口 / Plugin Interface Design with C ABI, Function Tables, and Controlled C++ Interfaces

4. CMake 构建完整插件项目 / Building a Complete Plugin Project with CMake

5. 插件架构设计模式:发现、生命周期、依赖与通信 / Plugin Architecture Patterns for Discovery, Lifecycle, Dependencies, and Communication

6. 跨平台插件开发:Windows / Linux / macOS 统一策略

7. 插件热加载与版本管理 / Plugin Hot Reloading and Version Management

8. 插件安全:沙箱、进程隔离与权限控制 / Plugin Security with Sandboxing, Process Isolation, and Permissions

9. 工业软件插件系统案例分析 / Industrial Software Plugin System Case Study

10. 综合实战:版本化 C ABI 插件系统 / Full Project: Versioned C ABI Plugin System

本页目录

跨平台插件开发:Windows / Linux / macOS 统一策略 ​

📅 创建时间:2026-07-13 🏷️ 标签:#跨平台 #Windows #Linux #macOS #条件编译 📚 前置知识:runtime dynamic loading, cmake plugin project


📋 本章目标 ​

  • 掌握三平台(Windows / Linux / macOS)动态库开发的差异速查
  • 学会用条件编译和宏封装统一三平台的动态加载 API
  • 理解 macOS 特有概念:Framework Bundle、@rpath、install_name_tool
  • 掌握跨平台 CMake 配置的最佳实践

第1部分:三平台差异速查表 ​

┌─────────────────────────────────────────────────────────────────────────────┐
│              三平台核心差异                                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  维度            │ Windows              │ Linux              │ macOS        │
│  ────────────────┼─────────────────────┼───────────────────┼───────────────│
│  动态库后缀      │ .dll                │ .so               │ .dylib        │
│  可执行后缀      │ .exe                │ (无)              │ (无) / .app   │
│  静态库后缀      │ .lib                │ .a                │ .a            │
│  导入库          │ .lib (必须有!)      │ 不需要            │ .tbd (可选)    │
│  加载函数        │ LoadLibrary         │ dlopen            │ dlopen        │
│  取符号          │ GetProcAddress      │ dlsym             │ dlsym         │
│  卸载            │ FreeLibrary         │ dlclose           │ dlclose        │
│  错误获取        │ GetLastError        │ dlerror           │ dlerror        │
│  符号导出宏      │ __declspec(         │ __attribute__     │ __attribute__ │
│                  │   dllexport)        │   ((visibility     │   ((visibility │
│                  │                     │    ("default")))   │    ("default")))│
│  符号导入宏      │ __declspec(         │ 默认              │ 默认          │
│                  │   dllimport)        │                   │               │
│  默认符号可见性  │ 隐藏(不导出)       │ 导出(全可见)     │ 导出(全可见)│
│  库搜索路径      │ .exe同级>PATH>系统   │ RPATH>LD_LIB...   │ @rpath>DYLD..│
│  环境变量        │ PATH                │ LD_LIBRARY_PATH   │ DYLD_LIBRARY  │
│                  │                     │                   │ _PATH         │
│  调试符号        │ .pdb(独立文件)     │ 嵌入ELF或.debug   │ .dSYM bundle  │
│  运行时库        │ vcruntime           │ libstdc++/libc++  │ libc++        │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
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

第2部分:统一封装 —— 一套代码编译三平台 ​

2.1 跨平台宏 ​

cpp
// plugin_loader.h
#pragma once

// ====== 1. 平台检测 ======
#if defined(_WIN32) || defined(_WIN64)
    #define PLATFORM_WINDOWS 1
#elif defined(__APPLE__)
    #define PLATFORM_MACOS 1
#elif defined(__linux__)
    #define PLATFORM_LINUX 1
#else
    #error "Unsupported platform"
#endif

// ====== 2. 动态库句柄类型 ======
#if PLATFORM_WINDOWS
    #include <windows.h>
    #define PLUGIN_HANDLE HMODULE
    #define PLUGIN_INVALID_HANDLE NULL
#else
    #include <dlfcn.h>
    #define PLUGIN_HANDLE void*
    #define PLUGIN_INVALID_HANDLE nullptr
#endif

// ====== 3. 统一的 API ======
inline PLUGIN_HANDLE plugin_load(const char* path) {
#if PLATFORM_WINDOWS
    return LoadLibraryA(path);
#else
    return dlopen(path, RTLD_NOW | RTLD_LOCAL);
#endif
}

inline void* plugin_getsym(PLUGIN_HANDLE h, const char* name) {
#if PLATFORM_WINDOWS
    return (void*)GetProcAddress(h, name);
#else
    return dlsym(h, name);
#endif
}

inline void plugin_close(PLUGIN_HANDLE h) {
#if PLATFORM_WINDOWS
    FreeLibrary(h);
#else
    dlclose(h);
#endif
}

inline const char* plugin_error() {
#if PLATFORM_WINDOWS
    static char buf[256];
    FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL,
                   GetLastError(), 0, buf, sizeof(buf), NULL);
    return buf;
#else
    return dlerror();
#endif
}

// ====== 4. 动态库文件后缀 ======
inline const char* plugin_extension() {
#if PLATFORM_WINDOWS
    return ".dll";
#elif PLATFORM_MACOS
    return ".dylib";
#else
    return ".so";
#endif
}
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

2.2 符号导出/导入宏 ​

cpp
// plugin_export.h
#pragma once

#if PLATFORM_WINDOWS
    // Windows: dllexport 导出, dllimport 导入
    #ifdef PLUGIN_EXPORTS
        #define PLUGIN_API __declspec(dllexport)
    #else
        #define PLUGIN_API __declspec(dllimport)
    #endif
#else
    // Linux/macOS: 用 visibility 控制
    #define PLUGIN_API __attribute__((visibility("default")))
#endif

// 用法:
// 编译插件时定义 PLUGIN_EXPORTS → dllexport / 显式 visible
// 使用插件时(宿主)不定义 → dllimport (Windows) / 默认 (Linux)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

第3部分:macOS 特殊处理 ​

3.1 Framework vs dylib ​

macOS 有两种动态库形态:

┌─────────────────────────────────────────────────────────────────────────────┐
│              macOS Framework vs dylib                                        │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  .dylib(类似 Linux .so)                                                    │
│  ┌───────────────────────────────────────────────────────────────┐         │
│  │ 单一文件:libfoo.dylib                                          │         │
│  │ 适合:命令行工具、后台服务、跨平台 C++ 库                        │         │
│  └───────────────────────────────────────────────────────────────┘         │
│                                                                             │
│  .framework Bundle                                                          │
│  ┌───────────────────────────────────────────────────────────────┐         │
│  │ Foo.framework/                                                 │         │
│  │ ├── Foo           ← 实际的动态库(Mach-O)                      │         │
│  │ ├── Headers/      ← 头文件(可选)                              │         │
│  │ ├── Resources/    ← 图标、图片、nib 等                         │         │
│  │ └── Versions/     ← 版本管理                                   │         │
│  │                                                               │         │
│  │ 适合:GUI 应用框架、系统级 Framework                            │         │
│  └───────────────────────────────────────────────────────────────┘         │
│                                                                             │
│  插件开发场景:用 .dylib 就够了,简单直接                                     │
│  Framework 主要用于系统 API 和 GUI 框架                                       │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
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

3.2 @rpath 和 install_name_tool ​

macOS 的动态库有一个"内嵌路径"(install name),记录了运行时应该在哪个位置找到这个 .dylib:

bash
# 查看 .dylib 的 install name
otool -D libfoo.dylib
# 输出:@rpath/libfoo.dylib

# @rpath = 可执行文件中记录的 runpath search paths
# 等价于 Linux 的 RPATH / RUNPATH

# 修改 install name
install_name_tool -id "@rpath/libfoo.dylib" libfoo.dylib

# 修改可执行文件对动态库的依赖路径
install_name_tool -change "/old/path/libfoo.dylib" \
                         "@rpath/libfoo.dylib" my_app

# 添加 @rpath 搜索路径
install_name_tool -add_rpath "@loader_path/../lib" my_app
# @loader_path = 加载者所在的目录(如果是可执行文件,就是 .app/Contents/MacOS)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

3.3 macOS 上的 CMake 配置 ​

cmake
if(APPLE)
    # 设置 install name
    set_target_properties(plugin_math PROPERTIES
        INSTALL_NAME_DIR "@rpath"
        MACOSX_RPATH ON
    )

    # .dylib 的版本信息
    set_target_properties(plugin_math PROPERTIES
        VERSION 1.0.0
        SOVERSION 1
    )
endif()
1
2
3
4
5
6
7
8
9
10
11
12
13

第4部分:跨平台 CMake 配置 ​

cmake
# 平台检测
if(WIN32)
    set(PLUGIN_SUFFIX ".dll")
    set(EXE_SUFFIX ".exe")
    add_compile_definitions(PLATFORM_WINDOWS)
elseif(APPLE)
    set(PLUGIN_SUFFIX ".dylib")
    set(EXE_SUFFIX "")
    add_compile_definitions(PLATFORM_MACOS)
else()
    set(PLUGIN_SUFFIX ".so")
    set(EXE_SUFFIX "")
    add_compile_definitions(PLATFORM_LINUX)
endif()

# 插件输出名称可以统一
set_target_properties(plugin_math PROPERTIES
    OUTPUT_NAME "math_plugin"
    SUFFIX "${PLUGIN_SUFFIX}"
)

# Windows: 链接 ws2_32, 设置 UNICODE
if(WIN32)
    target_link_libraries(plugin_host PRIVATE ws2_32)
    add_compile_definitions(UNICODE _UNICODE)
endif()

# Linux: 链接 dl, pthread
if(UNIX AND NOT APPLE)
    target_link_libraries(plugin_host PRIVATE ${CMAKE_DL_LIBS} pthread)
endif()

# macOS: 需要 Foundation 等(如果用了 Objective-C)
if(APPLE)
    target_link_libraries(plugin_host PRIVATE "-framework Foundation")
endif()
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

第5部分:不同平台的插件搜索路径惯例 ​

cpp
std::vector<std::string> get_default_plugin_paths() {
    std::vector<std::string> paths;

#if PLATFORM_WINDOWS
    // Windows: exe 同级目录下的 plugins/
    paths.push_back(get_exe_dir() + "\\plugins\\");

    // 用户 AppData
    char* appdata = getenv("APPDATA");
    if (appdata) paths.push_back(std::string(appdata) + "\\MyApp\\plugins\\");

    // 程序安装目录(注册表读取)
    paths.push_back("C:\\Program Files\\MyApp\\plugins\\");
#elif PLATFORM_MACOS
    // macOS: .app bundle 内
    paths.push_back(get_bundle_dir() + "/Contents/PlugIns/");

    // 用户 Library
    const char* home = getenv("HOME");
    if (home) paths.push_back(std::string(home) + "/Library/Application Support/MyApp/plugins/");

    // 系统 Library
    paths.push_back("/Library/Application Support/MyApp/plugins/");
#else
    // Linux: 可执行文件相对路径
    paths.push_back(get_exe_dir() + "/../lib/myapp/plugins/");

    // XDG 用户目录
    const char* xdg = getenv("XDG_DATA_HOME");
    if (!xdg) xdg = (getenv("HOME") + std::string("/.local/share")).c_str();
    paths.push_back(std::string(xdg) + "/myapp/plugins/");

    // 系统路径
    paths.push_back("/usr/lib/myapp/plugins/");
    paths.push_back("/usr/local/lib/myapp/plugins/");
#endif

    // 通用:环境变量
    const char* env = getenv("MYAPP_PLUGIN_PATH");
    if (env) {
        for (auto& p : split(env, ':')) paths.push_back(p);
    }

    return paths;
}
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
41
42
43
44
45

核心总结 ​

┌─────────────────────────────────────────────────────────────────────────────┐
│              跨平台插件速查                                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  Windows:  LoadLibrary / GetProcAddress / FreeLibrary                       │
│            .dll + .lib, dllexport/dllimport                                │
│                                                                             │
│  Linux:    dlopen / dlsym / dlclose, .so, visibility("default")            │
│                                                                             │
│  macOS:    dlopen / dlsym / dlclose, .dylib, @rpath                         │
│                                                                             │
│  统一封装:条件编译 #if PLATFORM_WINDOWS / #elif PLATFORM_LINUX             │
│                                                                             │
│  macOS 特殊工具:                                                             │
│    otool -L  = 查看依赖(类似 ldd)                                         │
│    install_name_tool = 修改 install name(类似 patchelf)                   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

章节测试 ​

测试1:三平台对比 ​

在 Windows 上用 __declspec(dllexport),在 Linux 上用什么?为什么 Linux 默认导出所有符号?

测试2:macOS @rpath ​

Linux 的 $ORIGIN 和 macOS 的 @rpath 有什么异同?

测试3:跨平台 CMake ​

如何让同一个 CMakeLists.txt 在三个平台上都正确构建插件?写出关键的 if/else 分支逻辑。


参考答案 ​

测试1答案 ​

答案:Linux 上用 __attribute__((visibility("default")))。Linux(GCC)默认导出所有符号是因为历史原因——传统 Unix 链接器就默认所有符号对外可见。而 Windows 默认隐藏所有符号,只导出显式标记的。现代 Linux 开发推荐加 -fvisibility=hidden 让 Linux 表现得像 Windows 一样(默认隐藏 + 显式导出)。

测试2答案 ​

答案:两者都是解决"程序怎么找到自己的动态库"的机制。$ORIGIN 是 Linux 的 RPATH 变量,指"可执行文件所在目录"。@rpath 是 macOS 的 install name 前缀,也需要配合可执行文件中的 rpath 条目使用。区别在于:(1) Linux 的 $ORIGIN 是加载器内置支持的变量;(2) macOS 的 @rpath 需要显式用 -rpath 链接选项或 install_name_tool -add_rpath 添加搜索路径;(3) 用法类似——都是为了让程序绑定的库位置是相对路径。

测试3答案 ​

答案:

cmake
if(WIN32)
    set(PLUGIN_EXT ".dll")
    target_compile_definitions(plugin_math PRIVATE PLUGIN_EXPORTS)
elseif(APPLE)
    set(PLUGIN_EXT ".dylib")
    set_target_properties(plugin_math PROPERTIES
        INSTALL_NAME_DIR "@rpath"
        MACOSX_RPATH ON)
else()
    set(PLUGIN_EXT ".so")
    target_link_libraries(plugin_host PRIVATE ${CMAKE_DL_LIBS})
endif()
set_target_properties(plugin_math PROPERTIES SUFFIX "${PLUGIN_EXT}")
1
2
3
4
5
6
7
8
9
10
11
12
13

相关笔记 ​

  • runtime dynamic loading - 运行时动态加载 API 详解
  • cmake plugin project - CMake 构建插件项目
  • hot reload versioning - 热加载与版本管理

下一步学习 ​

  • [ ] 阅读 11 - 插件热加载与版本管理

学习状态:🟡 开始学习

工程深化:把本篇知识落到项目里 ​

这一节不是为了凑篇幅,而是把《跨平台插件开发:Windows / Linux / macOS 统一策略》从“知道概念”推进到“能在项目里稳定使用”。 阅读时可以把每个知识点都追问成四件事:它保护什么边界,失败时有什么现象,怎样最小复现,怎样写进团队流程。

text
concept -> boundary -> failure signal -> minimal proof -> project rule
1

工程切片 1:最小可复现样例 ​

**场景。**围绕 DLL 建一个很小的案例,不要一开始就放进完整业务系统。把问题压缩到一个可以提交给同事的目录,保留源码、构建命令、版本输出和预期现象。

**边界。**先判断这里讨论的是 DLL、导入库 还是 PDB 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> DLL -> 导入库 -> PDB -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 2:接口边界 ​

**场景。**围绕 导入库 建一个很小的案例,不要一开始就放进完整业务系统。写清调用方能依赖什么、不能依赖什么,并把隐式假设转换成命名函数、配置项或测试。

**边界。**先判断这里讨论的是 导入库、PDB 还是 运行库 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 导入库 -> PDB -> 运行库 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 3:失败注入 ​

**场景。**围绕 PDB 建一个很小的案例,不要一开始就放进完整业务系统。故意制造一个常见错误,让日志、断言或测试先失败,再用修复后的证据说明规则生效。

**边界。**先判断这里讨论的是 PDB、运行库 还是 导出符号 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> PDB -> 运行库 -> 导出符号 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 4:跨平台差异 ​

**场景。**围绕 运行库 建一个很小的案例,不要一开始就放进完整业务系统。至少比较 Windows、Linux 或不同编译器下的一个差异,记录差异属于标准、实现还是平台约定。

**边界。**先判断这里讨论的是 运行库、导出符号 还是 部署目录 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 运行库 -> 导出符号 -> 部署目录 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 5:性能观察 ​

**场景。**围绕 导出符号 建一个很小的案例,不要一开始就放进完整业务系统。用小规模和压力规模各跑一次,区分算法成本、同步成本、I/O 成本和工具链配置成本。

**边界。**先判断这里讨论的是 导出符号、部署目录 还是 DLL 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 导出符号 -> 部署目录 -> DLL -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 6:生命周期 ​

**场景。**围绕 部署目录 建一个很小的案例,不要一开始就放进完整业务系统。标出资源创建、转移、共享、停止和销毁的顺序,特别关注错误返回和提前退出路径。

**边界。**先判断这里讨论的是 部署目录、DLL 还是 导入库 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 部署目录 -> DLL -> 导入库 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 7:诊断证据 ​

**场景。**围绕 DLL 建一个很小的案例,不要一开始就放进完整业务系统。保留命令、日志、符号、栈、测试输出或截图,让结论可以被另一个环境重新验证。

**边界。**先判断这里讨论的是 DLL、导入库 还是 PDB 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> DLL -> 导入库 -> PDB -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 8:维护策略 ​

**场景。**围绕 导入库 建一个很小的案例,不要一开始就放进完整业务系统。把一次性经验沉淀到 README、CI、脚本、示例工程或检查表里,避免只存在个人记忆中。

**边界。**先判断这里讨论的是 导入库、PDB 还是 运行库 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 导入库 -> PDB -> 运行库 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

本篇收束 ​

掌握《跨平台插件开发:Windows / Linux / macOS 统一策略》的标志,不是记住所有名词,而是能把 DLL、导入库、PDB、运行库、导出符号、部署目录 放进一条可验证的工程链路。 先用最小样例建立判断,再用测试和脚本固定判断,最后把失败证据留给未来的自己和团队。

最后更新于:

Pager
上一篇5. 插件架构设计模式:发现、生命周期、依赖与通信 / Plugin Architecture Patterns for Discovery, Lifecycle, Dependencies, and Communication
下一篇7. 插件热加载与版本管理 / Plugin Hot Reloading and Version Management

持续记录,持续成长

Copyright © Tidenflow