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

本页目录

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

📅 创建时间:2026-07-13 🏷️ 标签:#插件架构 #设计模式 #生命周期 #依赖管理 #事件总线 📚 前置知识:plugin interface design, cmake plugin project


📋 本章目标 ​

  • 掌握四种插件发现机制及其适用场景
  • 理解插件完整生命周期状态机的设计
  • 学会管理插件间依赖:拓扑排序与循环检测
  • 理解插件间通信的三种模式:中介者、服务定位器、事件总线
  • 通过 VS Code / QGIS / 游戏 Mod 系统理解真实架构选择

第1部分:插件发现 —— 宿主怎么找到插件? ​

1.1 四种发现机制 ​

┌─────────────────────────────────────────────────────────────────────────────┐
│              四种插件发现机制                                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  方法1:约定目录扫描(最常用,推荐)                                           │
│  ┌───────────────────────────────────────────────────────────────┐         │
│  │ plugins/                                                       │         │
│  │ ├── plugin_math.so                                             │         │
│  │ ├── plugin_text.so                                             │         │
│  │ └── disabled/              ← 不想加载的放子目录或不扫描         │         │
│  │     └── plugin_old.so                                          │         │
│  │                                                               │         │
│  │ std::vector<std::string> scan_plugins(const char* dir) {      │         │
│  │     for (auto& entry : fs::directory_iterator(dir)) {         │         │
│  │         if (entry.path().extension() == ".so" ||              │         │
│  │             entry.path().extension() == ".dll") {             │         │
│  │             results.push_back(entry.path());                  │         │
│  │         }                                                      │         │
│  │     }                                                          │         │
│  │     return results;                                           │         │
│  │ }                                                              │         │
│  └───────────────────────────────────────────────────────────────┘         │
│                                                                             │
│  方法2:配置文件注册                                                         │
│  ┌───────────────────────────────────────────────────────────────┐         │
│  │ // plugins.json                                                 │         │
│  │ {                                                              │         │
│  │   "plugins": [                                                 │         │
│  │     {"name": "math",  "path": "./plugins/libmath.so",          │         │
│  │      "enabled": true, "priority": 10},                         │         │
│  │     {"name": "text",  "path": "./plugins/libtext.so",          │         │
│  │      "enabled": true, "priority": 5}                           │         │
│  │   ]                                                            │         │
│  │ }                                                              │         │
│  │ 优点:可以排优先级、控制启用/禁用                                 │         │
│  └───────────────────────────────────────────────────────────────┘         │
│                                                                             │
│  方法3:注册表(Windows only)                                               │
│  ┌───────────────────────────────────────────────────────────────┐         │
│  │ 插件安装时在注册表中写 CLSID 和 DLL 路径                         │         │
│  │ 宿主通过 COM 或注册表查找                                        │         │
│  │ 适用于:需要系统级注册的大型桌面软件                               │         │
│  └───────────────────────────────────────────────────────────────┘         │
│                                                                             │
│  方法4:环境变量                                                             │
│  ┌───────────────────────────────────────────────────────────────┐         │
│  │ export MYAPP_PLUGIN_PATH=/opt/plugins:/home/user/.myapp/plugins│         │
│  │ 适用于:开发者调试、自定义部署                                    │         │
│  └───────────────────────────────────────────────────────────────┘         │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
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

第2部分:插件生命周期管理 ​

2.1 状态机设计 ​

┌─────────────────────────────────────────────────────────────────────────────┐
│              插件生命周期状态机                                                │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│                    ┌──────────┐                                              │
│                    │ UNLOADED │  ← 初始状态                                  │
│                    └────┬─────┘                                              │
│                         │ dlopen / LoadLibrary                              │
│                         ▼                                                   │
│                    ┌──────────┐                                              │
│                    │  LOADED  │  ← 库已在内存中,但还没有执行任何初始化       │
│                    └────┬─────┘                                              │
│                         │ dlsym create + plugin->init()                       │
│                         ▼                                                   │
│                    ┌──────────┐                                              │
│                    │INITIALIZED│ ← 插件已经就绪,可以接受调用                 │
│                    └────┬─────┘                                              │
│                         │ 正常调用...                                        │
│                         │ 或 plugin->shutdown()                              │
│                         ▼                                                   │
│                    ┌──────────┐                                              │
│                    │ SHUTDOWN │  ← 清理资源,准备卸载                         │
│                    └────┬─────┘                                              │
│                         │ dlclose / FreeLibrary                              │
│                         ▼                                                   │
│                    ┌──────────┐                                              │
│                    │ UNLOADED │  ← 回到初始状态                               │
│                    └──────────┘                                              │
│                                                                             │
│  错误状态:                                                                  │
│  UNLOADED → LOADED → INITIALIZED 过程中任何步骤失败                           │
│    → ERROR(记录错误信息,清理已分配资源)                                    │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
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

2.2 代码实现——带生命周期的接口 ​

cpp
class IPlugin {
public:
    virtual ~IPlugin() = default;

    // 生命周期
    virtual bool initialize() = 0;    // 初始化(分配资源、注册服务)
    virtual void shutdown() = 0;      // 关闭(释放资源、注销服务)

    // 元信息
    virtual const char* getName() = 0;
    virtual const char* getVersion() = 0;

    // 依赖声明
    virtual const char** getDependencies() { return nullptr; }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cpp
// 宿主中的插件管理器
class PluginManager {
    struct PluginEntry {
        void* handle;
        IPlugin* instance;
        enum State { UNLOADED, LOADED, INITIALIZED, ERROR } state;
        std::string path;
    };

public:
    bool loadPlugin(const std::string& path) {
        auto& entry = entries.emplace_back();
        entry.path = path;
        entry.state = LOADED;

        // Step 1: 加载库
        entry.handle = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL);
        if (!entry.handle) {
            entry.state = ERROR;
            return false;
        }

        // Step 2: 获取工厂函数
        auto create = (IPlugin*(*)())dlsym(entry.handle, "create_plugin");
        if (!create) { dlclose(entry.handle); entry.state = ERROR; return false; }

        // Step 3: 创建实例
        entry.instance = create();
        entry.state = LOADED;

        // Step 4: 初始化
        if (!entry.instance->initialize()) {
            delete entry.instance;
            dlclose(entry.handle);
            entry.state = ERROR;
            return false;
        }
        entry.state = INITIALIZED;
        return true;
    }

    void shutdownAll() {
        for (auto it = entries.rbegin(); it != entries.rend(); ++it) {
            if (it->state == INITIALIZED) {
                it->instance->shutdown();
                delete it->instance;     // 插件内部分配,插件内部释放
                dlclose(it->handle);
                it->state = UNLOADED;
            }
        }
    }
};
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

第3部分:插件间依赖管理 ​

3.1 依赖声明 ​

cpp
// 插件声明自己的依赖
extern "C" const char** get_dependencies() {
    static const char* deps[] = {"plugin_math", "plugin_database", nullptr};
    return deps;
}

extern "C" const char* get_min_version(const char* plugin_name) {
    if (strcmp(plugin_name, "plugin_math") == 0) return "1.0.0";
    if (strcmp(plugin_name, "plugin_database") == 0) return "2.0.0";
    return "0.0.0";
}
1
2
3
4
5
6
7
8
9
10
11

3.2 拓扑排序加载 ​

┌─────────────────────────────────────────────────────────────────────────────┐
│              依赖拓扑排序                                                     │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  插件依赖图:                                                                 │
│                                                                             │
│  plugin_report ────→ plugin_data ────→ plugin_database                      │
│       │                                    ↑                                │
│       └────────→ plugin_math ──────────────┘                                │
│                                                                             │
│  正确的加载顺序:                                                             │
│  1. plugin_database(没有依赖)                                               │
│  2. plugin_data(依赖 database)                                              │
│  3. plugin_math(依赖 database)                                              │
│  4. plugin_report(依赖 data + math)                                         │
│                                                                             │
│  算法:Kahn's algorithm (BFS 拓扑排序)                                        │
│  • 构建入度表                                                                │
│  • 入度为 0 的插件先加载                                                      │
│  • 加载后减少依赖它的插件的入度                                                │
│  • 循环依赖 → 加载失败                                                        │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

第4部分:插件间通信 ​

4.1 三种通信模式对比 ​

┌─────────────────────────────────────────────────────────────────────────────┐
│              插件间通信模式                                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  模式1:通过宿主中转(Mediator 中介者)                                       │
│  ┌───────────────────────────────────────────────────────────────┐         │
│  │                                                                         │
│  │  PluginA ──→ Host (PluginManager) ──→ PluginB                          │
│  │                                                                         │
│  │  Host 提供一个 request(const char* target, const char* msg,             │
│  │                       void* data) 方法                                    │
│  │  PluginA 调用 host->request("PluginB", "compute", &params)              │
│  │  Host 找到 PluginB,调用 PluginB->handleRequest("compute", &params)     │
│  │                                                                         │
│  │  ✅ 解耦:插件不直接知道彼此                                               │
│  │  ❌ Host 成了瓶颈(所有通信都经过它)                                       │
│  └───────────────────────────────────────────────────────────────┘         │
│                                                                             │
│  模式2:服务定位器(Service Locator)                                         │
│  ┌───────────────────────────────────────────────────────────────┐         │
│  │                                                                         │
│  │  Host 提供 ServiceRegistry                                              │
│  │                                                                         │
│  │  PluginA 注册:registry->register("logger", myLogger)                   │
│  │  PluginB 查找:auto logger = registry->get<ILogger>("logger")           │
│  │                                                                         │
│  │  ✅ 灵活:插件可以按需查找服务                                              │
│  │  ✅ 松耦合:插件只依赖接口类型,不依赖具体插件                               │
│  │  ❌ 服务不存在时的处理需要显式处理                                          │
│  └───────────────────────────────────────────────────────────────┘         │
│                                                                             │
│  模式3:事件总线(Event Bus / Pub-Sub)                                       │
│  ┌───────────────────────────────────────────────────────────────┐         │
│  │                                                                         │
│  │  Host 提供 EventBus                                                     │
│  │                                                                         │
│  │  PluginA 发布:bus->publish("file.saved", {path: "/tmp/test.txt"})     │
│  │  PluginB 订阅:bus->subscribe("file.saved", onFileSaved)                │
│  │  PluginC 也订阅:bus->subscribe("file.*", onFileChanged)                │
│  │                                                                         │
│  │  ✅ 最解耦:发布者和订阅者完全互相不知道                                    │
│  │  ✅ 一对多通信天然支持                                                     │
│  │  ❌ 调试困难:事件追踪不直观                                                │
│  │  ❌ 顺序不确定:多个订阅者的执行顺序不保证                                   │
│  └───────────────────────────────────────────────────────────────┘         │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
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

4.2 服务定位器完整示例 ​

cpp
// 服务注册中心
class ServiceRegistry {
public:
    template<typename T>
    void registerService(const char* name, T* service) {
        services[name] = static_cast<void*>(service);
    }

    template<typename T>
    T* getService(const char* name) {
        auto it = services.find(name);
        return (it != services.end()) ? static_cast<T*>(it->second) : nullptr;
    }

private:
    std::unordered_map<std::string, void*> services;
};

// 在插件初始化时:
class MathPlugin : public IPlugin {
    bool initialize() override {
        // 注册自己提供的服务
        registry->registerService<ICalculator>("calculator", this);
        // 查找自己需要的服务
        auto logger = registry->getService<ILogger>("logger");
        if (!logger) return false;  // 依赖的服务不存在
        return true;
    }
};
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

第5部分:真实案例分析 ​

┌─────────────────────────────────────────────────────────────────────────────┐
│              三种典型的插件架构                                                │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  VS Code 扩展系统:                                                          │
│  ┌───────────────────────────────────────────────────────────────┐         │
│  │ • 进程隔离:每个扩展在独立的 Extension Host 进程中运行           │         │
│  │ • JSON 声明:package.json 声明 activationEvents                 │         │
│  │ • 延迟激活:只在匹配的 activationEvents 触发时才加载扩展         │         │
│  │ • API 注入:宿主向扩展注入 vscode API 对象                     │         │
│  │ • 安全:进程隔离 + 权限声明                                     │         │
│  └───────────────────────────────────────────────────────────────┘         │
│                                                                             │
│  QGIS 插件系统:                                                             │
│  ┌───────────────────────────────────────────────────────────────┐         │
│  │ • Python 主导:绝大多数插件是 Python(pyqgis)                   │         │
│  │ • C++ Provider:底层数据处理插件可以用 C++                      │         │
│  │ • 约定目录:~/.qgis/python/plugins/                           │         │
│  │ • metadata.txt:声明插件元信息                                  │         │
│  │ • 热加载:Plugin Reloader 插件支持不重启加载                    │         │
│  └───────────────────────────────────────────────────────────────┘         │
│                                                                             │
│  游戏引擎 Mod 系统(以 Unreal/Unity 为参考):                                │
│  ┌───────────────────────────────────────────────────────────────┐         │
│  │ • 资源 + 代码:Mod = .pak(资产) + .dll/.so(代码)               │         │
│  │ • Mount/Unmount:运行时加载/卸载 Mod                           │         │
│  │ • 依赖声明:Mod 可以在 .json 中声明对其他 Mod 的依赖           │         │
│  │ • 补丁系统:先后加载的 Mod 覆盖前面的资源                       │         │
│  └───────────────────────────────────────────────────────────────┘         │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
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

核心总结 ​

┌─────────────────────────────────────────────────────────────────────────────┐
│              插件架构核心模式                                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  发现:约定目录扫描(最简单)→ 配置文件(可控)→ 注册表(企业级)               │
│                                                                             │
│  生命周期:UNLOADED → LOADED → INITIALIZED → SHUTDOWN → UNLOADED            │
│                                                                             │
│  依赖:拓扑排序加载 + 循环依赖检测                                            │
│                                                                             │
│  通信三模式:                                                                 │
│    • 中介者(Host 中转)—— 简单但瓶颈                                         │
│    • 服务定位器 —— 灵活松耦合,推荐                                           │
│    • 事件总线 —— 最解耦,适合一对多                                           │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

章节测试 ​

测试1:发现机制 ​

四种插件发现机制分别适用于什么场景?为什么"约定目录扫描"是最常用的?

测试2:生命周期 ​

为什么插件需要 initialize() 和 shutdown() 两个独立步骤?只在 dlopen 时初始化和 dlclose 时清理不行吗?

测试3:依赖 ​

A 依赖 B,B 依赖 C。加载这三个插件的正确顺序是什么?如果 C 也声明依赖 A 呢?

测试4:通信模式 ​

服务定位器和事件总线有什么区别?什么场景下该用哪个?


参考答案 ​

测试1答案 ​

答案:(1) 约定目录扫描——最简单,用户只需把 .so/.dll 放到 plugins/ 目录;(2) 配置文件——适合需要控制启用/禁用和优先级的企业环境;(3) 注册表——Windows 桌面软件需要 COM 注册的场景;(4) 环境变量——适合开发和调试。约定目录最常用因为零配置,用户体验最好。

测试2答案 ​

答案:分离 initialize 和 shutdown 是因为:(1) dlopen 只加载库文件,插件的初始化可能需要访问其他已加载的服务,应该等所有插件加载完再初始化;(2) dlclose 是释放库文件,插件的清理(如保存状态、注销服务)应该在 shutdown 中显式完成;(3) 生命周期分离让错误恢复更容易——init 失败可以只清理这个插件而不影响其他。

测试3答案 ​

答案:加载顺序:C → B → A(拓扑排序)。如果 C 也依赖 A → 循环依赖 → 无法确定加载顺序 → 应该拒绝加载。循环依赖表示架构设计有问题,需要重构(比如提取公共接口到一个单独的模块)。

测试4答案 ​

答案:服务定位器是"请求-响应"模式——PluginB 主动查询"谁提供 ILogger 服务"并获取实例。事件总线是"发布-订阅"模式——Publisher 不知道谁在听,Subscriber 不知道谁在发布。选服务定位器:需要同步的、请求-响应式的交互(如"帮我计算 3+5")。选事件总线:一对多的通知(如"文件已保存,所有关心的插件请更新")。


相关笔记 ​

  • plugin interface design - 插件接口设计
  • cmake plugin project - CMake 构建插件项目
  • hot reload versioning - 热加载需要依赖生命周期的支持

下一步学习 ​

  • [ ] 阅读 10 - 跨平台插件开发

学习状态:🟡 开始学习

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

这一节不是为了凑篇幅,而是把《插件架构设计模式:发现、生命周期、依赖与通信 / Plugin Architecture Patterns for Discovery, Lifecycle, Dependencies, and Communication》从“知道概念”推进到“能在项目里稳定使用”。 阅读时可以把每个知识点都追问成四件事:它保护什么边界,失败时有什么现象,怎样最小复现,怎样写进团队流程。

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

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

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

**边界。**先判断这里讨论的是 插件发现、加载器 还是 契约 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 插件发现 -> 加载器 -> 契约 -> observable result
1

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

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

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

工程切片 2:接口边界 ​

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

**边界。**先判断这里讨论的是 加载器、契约 还是 生命周期 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 加载器 -> 契约 -> 生命周期 -> observable result
1

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

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

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

工程切片 3:失败注入 ​

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

**边界。**先判断这里讨论的是 契约、生命周期 还是 隔离 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 契约 -> 生命周期 -> 隔离 -> observable result
1

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

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

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

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

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

**边界。**先判断这里讨论的是 生命周期、隔离 还是 诊断 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 生命周期 -> 隔离 -> 诊断 -> observable result
1

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

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

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

工程切片 5:性能观察 ​

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

**边界。**先判断这里讨论的是 隔离、诊断 还是 插件发现 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 隔离 -> 诊断 -> 插件发现 -> observable result
1

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

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

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

工程切片 6:生命周期 ​

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

**边界。**先判断这里讨论的是 诊断、插件发现 还是 加载器 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 诊断 -> 插件发现 -> 加载器 -> observable result
1

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

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

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

本篇收束 ​

掌握《插件架构设计模式:发现、生命周期、依赖与通信 / Plugin Architecture Patterns for Discovery, Lifecycle, Dependencies, and Communication》的标志,不是记住所有名词,而是能把 插件发现、加载器、契约、生命周期、隔离、诊断 放进一条可验证的工程链路。 先用最小样例建立判断,再用测试和脚本固定判断,最后把失败证据留给未来的自己和团队。

最后更新于:

Pager
上一篇4. CMake 构建完整插件项目 / Building a Complete Plugin Project with CMake
下一篇6. 跨平台插件开发:Windows / Linux / macOS 统一策略

持续记录,持续成长

Copyright © Tidenflow