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

本页目录

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

1. 项目目标 ​

这一篇把前面的插件工程知识收束成一个更稳的实战项目:数据处理宿主 datahost 可以从 plugins/ 目录发现插件,但不会盲目加载任意动态库。每个插件必须携带 manifest,通过签名或 hash 策略,导出 plugin_query_api_v1,并返回版本化函数表。

目标不是写一个最大功能 demo,而是写一条完整链路:

text
discover -> manifest precheck -> load -> query api -> negotiate
         -> init -> register -> run -> cancel -> shutdown -> unload
1
2

2. 为什么不用裸 C++ 接口作为收束项目 ​

如果综合项目最后仍然返回 IDataProcessor*,读者会误以为纯虚接口是默认安全边界。它在受控 ABI 家族里可以使用,但不适合作为跨编译器、跨供应商、长期兼容的默认方案。

本项目使用 C ABI 函数表,因为它能把版本、结构体大小、能力、错误码和所有权写进接口本身。

3. 项目文件结构 ​

text
datahost/
  CMakeLists.txt
  include/
    plugin_api.h
    plugin_manifest.h
    plugin_loader.h
  src/
    main.cpp
    plugin_loader.cpp
    manifest_reader.cpp
  plugins/
    uppercase/
      plugin.json
      uppercase.cpp
    linecount/
      plugin.json
      linecount.cpp
  tests/
    old_plugin/
    bad_manifest/
    missing_symbol/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

4. 公共 ABI 头文件 ​

cpp
#pragma once
#include <stddef.h>
#include <stdint.h>

#ifdef _WIN32
  #define DP_EXPORT extern "C" __declspec(dllexport)
#else
  #define DP_EXPORT extern "C" __attribute__((visibility("default")))
#endif

enum dp_status {
    DP_OK = 0,
    DP_INVALID_ARGUMENT = 1,
    DP_UNSUPPORTED_API = 2,
    DP_BUFFER_TOO_SMALL = 3,
    DP_CANCELLED = 4,
    DP_INTERNAL_ERROR = 5
};

enum dp_capability : uint64_t {
    DP_CAP_STREAMING = 1ull << 0,
    DP_CAP_CANCEL    = 1ull << 1,
    DP_CAP_STATEFUL  = 1ull << 2
};

struct dp_host_services_v1 {
    uint32_t api_version;
    size_t struct_size;
    void (*log)(int level, const char* message);
    void* (*alloc)(size_t bytes);
    void (*free)(void* ptr);
};

struct dp_plugin_descriptor_v1 {
    uint32_t api_version;
    size_t struct_size;
    const char* id;
    const char* name;
    const char* version;
    uint64_t capabilities;
};

struct dp_plugin_api_v1 {
    uint32_t api_version;
    size_t struct_size;
    dp_status (*init)(const dp_host_services_v1* host);
    dp_status (*describe)(dp_plugin_descriptor_v1* out);
    dp_status (*process)(const char* input, char* output, size_t output_size);
    dp_status (*cancel)(uint64_t request_id);
    dp_status (*shutdown)();
};

DP_EXPORT dp_status plugin_query_api_v1(
    const dp_host_services_v1* host,
    dp_plugin_api_v1* out_api);
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

5. manifest 预校验 ​

json
{
  "id": "com.example.uppercase",
  "name": "Uppercase Processor",
  "version": "1.2.0",
  "api": 1,
  "library": "uppercase_plugin",
  "sha256": "filled-by-release-pipeline",
  "capabilities": ["cancel"],
  "permissions": ["read-input", "write-output"]
}
1
2
3
4
5
6
7
8
9
10

宿主在加载动态库之前读取 manifest。至少检查:

  • 插件 id 是否在允许目录策略内;
  • api 是否在宿主支持范围内;
  • library 文件名是否没有路径穿越;
  • sha256 是否匹配;
  • permissions 是否被当前工作流允许;
  • version 是否满足依赖或黑名单策略。

6. 加载器状态机 ​

text
Discovered
  -> ManifestAccepted
  -> LibraryLoaded
  -> ApiQueried
  -> Initialized
  -> Registered
  -> Running
  -> Quiescing
  -> Shutdown
  -> Unloaded
1
2
3
4
5
6
7
8
9
10

每个状态失败都要有恢复策略。LibraryLoaded 后失败必须关闭句柄;Initialized 后失败必须调用 shutdown;Running 中取消必须先进入 quiescing,不能直接卸载。

7. 宿主加载伪代码 ​

cpp
loaded_plugin load_one(const manifest& m) {
    validate_manifest_before_load(m);
    auto lib = open_library(m.resolved_library_path);
    auto query = lib.symbol<query_api_fn>("plugin_query_api_v1");

    dp_host_services_v1 host{
        .api_version = 1,
        .struct_size = sizeof(dp_host_services_v1),
        .log = host_log,
        .alloc = host_alloc,
        .free = host_free
    };

    dp_plugin_api_v1 api{};
    api.api_version = 1;
    api.struct_size = sizeof(dp_plugin_api_v1);

    if (query(&host, &api) != DP_OK) {
        throw load_error("api negotiation failed");
    }
    if (api.api_version != 1 || api.struct_size < offsetof(dp_plugin_api_v1, shutdown) + sizeof(api.shutdown)) {
        throw load_error("incomplete api table");
    }
    if (api.init(&host) != DP_OK) {
        throw load_error("plugin init failed");
    }
    return loaded_plugin{std::move(lib), api, m};
}
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

8. 示例插件实现 ​

cpp
static dp_status uppercase_process(const char* input, char* output, size_t output_size) {
    if (!input || !output || output_size == 0) return DP_INVALID_ARGUMENT;
    size_t n = 0;
    for (; input[n] != '\0'; ++n) {
        if (n + 1 >= output_size) return DP_BUFFER_TOO_SMALL;
        char c = input[n];
        output[n] = (c >= 'a' && c <= 'z') ? char(c - 'a' + 'A') : c;
    }
    output[n] = '\0';
    return DP_OK;
}

DP_EXPORT dp_status plugin_query_api_v1(
    const dp_host_services_v1* host,
    dp_plugin_api_v1* out_api) {
    if (!host || !out_api) return DP_INVALID_ARGUMENT;
    if (host->api_version != 1) return DP_UNSUPPORTED_API;
    if (out_api->struct_size < sizeof(dp_plugin_api_v1)) return DP_UNSUPPORTED_API;

    out_api->api_version = 1;
    out_api->struct_size = sizeof(dp_plugin_api_v1);
    out_api->init = uppercase_init;
    out_api->describe = uppercase_describe;
    out_api->process = uppercase_process;
    out_api->cancel = uppercase_cancel;
    out_api->shutdown = uppercase_shutdown;
    return DP_OK;
}
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

9. 取消、关闭与卸载 ​

卸载前必须满足:

  1. 不再接受新请求;
  2. 已发出 cancel 或 deadline;
  3. 活动调用引用计数归零;
  4. 插件撤销所有宿主回调;
  5. 插件线程已经 join;
  6. shutdown 返回成功或进入隔离失败状态;
  7. 最后才关闭动态库句柄。

Windows 上已加载 DLL 通常不能被覆盖,因此热更新要复制到版本化文件名,例如 uppercase-1.2.0-<hash>.dll,新版本加载成功后再切换路由,旧版本等引用归零后释放。

10. 测试矩阵 ​

测试做什么应该失败的反例
bad_manifest加载前拒绝非法 manifest路径穿越、hash 不匹配
missing_symbol缺少 plugin_query_api_v1返回清晰加载错误
old_pluginv1 插件在新宿主运行API 协商成功
too_new_plugin插件要求 api v99拒绝并提示版本
cancel_running运行中取消不卸载仍在执行的库
shutdown_failureshutdown 返回错误标记隔离,不继续热替换

11. CMake 边界 ​

插件 target 应使用 MODULE 或平台明确的 SHARED 策略,输出到版本化插件目录。基础 CMake 语法请回到 11 和 08;这里关注插件边界:

cmake
add_library(uppercase MODULE plugins/uppercase/uppercase.cpp)
target_include_directories(uppercase PRIVATE include)
target_compile_definitions(uppercase PRIVATE DP_PLUGIN_BUILD=1)
set_target_properties(uppercase PROPERTIES
  PREFIX ""
  LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins/uppercase-1.2.0")
1
2
3
4
5
6

12. 发布证据 ​

每个插件包至少保留:

  • manifest;
  • 动态库;
  • sha256;
  • 符号导出报告;
  • ABI/API 版本;
  • 权限声明;
  • 最小宿主 smoke test;
  • shutdown/unload 测试日志;
  • 回滚版本。

13. 练习 ​

  1. 给 dp_plugin_api_v1 增加可选 streaming 能力,要求老插件仍可加载。
  2. 写一个 manifest hash 不匹配的插件,确认宿主不会加载动态库。
  3. 模拟插件 process 卡住,验证 cancel 和 unload 顺序。
  4. 在 Windows 上用版本化 DLL 文件名实现热替换。
  5. 把 plugin_query_api_v1 缺失的错误写成用户可理解的诊断。

14. 总结 ​

一个完整插件系统不是“扫描目录 + dlopen + 调函数”这么简单。真正重要的是发现前校验、接口协商、生命周期、取消、关闭、卸载、安全和发布证据。C ABI 函数表让这些约束都能写进稳定边界;C++ 复杂性留在插件内部,边界保持简单。

返回插件工程路线

15. 工程深化:从示例走向可维护插件生态 ​

《版本化 C ABI 插件系统》真正要训练的不是记住某个 API 名字,而是能把插件边界写成长期可执行的工程契约。下面这些切片可以作为 code review、实验和发布检查的素材。

text
design contract -> implementation -> failure injection -> CI evidence -> release rule
1

深化切片 1:发现阶段 ​

**工程问题。**扫描目录时只收集候选路径,不加载库。先做扩展名、目录白名单、manifest 名称和路径规范化检查。

**最小实验。**建立一个只包含宿主、一个插件、一个 manifest 和一条构建命令的小目录。先让正确路径通过,再故意破坏一个变量,例如版本号、结构体大小、入口符号、权限声明、线程关闭或输出缓冲区。

**观察证据。**记录宿主日志、动态加载错误、返回状态码、插件 id、api version、capabilities 和当前生命周期状态。错误消息应能让维护者知道失败发生在发现、加载、协商、初始化、运行、关闭还是卸载阶段。

text
candidate plugin
  -> check one boundary
  -> inject one failure
  -> record one reproducible proof
1
2
3
4

**复盘问题。**如果这个插件由第三方供应商构建、两年后升级、或在 Windows/Linux 两个平台发布,这条规则是否仍然能保护宿主?如果不能,应把它升级为 manifest 字段、ABI policy、CI 测试或加载器状态检查。

深化切片 2:manifest 阶段 ​

**工程问题。**读取 id、version、api、library、sha256、permissions、capabilities。任何字段缺失都应在加载前失败。

**最小实验。**建立一个只包含宿主、一个插件、一个 manifest 和一条构建命令的小目录。先让正确路径通过,再故意破坏一个变量,例如版本号、结构体大小、入口符号、权限声明、线程关闭或输出缓冲区。

**观察证据。**记录宿主日志、动态加载错误、返回状态码、插件 id、api version、capabilities 和当前生命周期状态。错误消息应能让维护者知道失败发生在发现、加载、协商、初始化、运行、关闭还是卸载阶段。

text
candidate plugin
  -> check one boundary
  -> inject one failure
  -> record one reproducible proof
1
2
3
4

**复盘问题。**如果这个插件由第三方供应商构建、两年后升级、或在 Windows/Linux 两个平台发布,这条规则是否仍然能保护宿主?如果不能,应把它升级为 manifest 字段、ABI policy、CI 测试或加载器状态检查。

深化切片 3:信任阶段 ​

**工程问题。**校验 hash、签名或来源策略。企业插件生态还要记录发布者、证书、吊销列表和允许加载的 channel。

**最小实验。**建立一个只包含宿主、一个插件、一个 manifest 和一条构建命令的小目录。先让正确路径通过,再故意破坏一个变量,例如版本号、结构体大小、入口符号、权限声明、线程关闭或输出缓冲区。

**观察证据。**记录宿主日志、动态加载错误、返回状态码、插件 id、api version、capabilities 和当前生命周期状态。错误消息应能让维护者知道失败发生在发现、加载、协商、初始化、运行、关闭还是卸载阶段。

text
candidate plugin
  -> check one boundary
  -> inject one failure
  -> record one reproducible proof
1
2
3
4

**复盘问题。**如果这个插件由第三方供应商构建、两年后升级、或在 Windows/Linux 两个平台发布,这条规则是否仍然能保护宿主?如果不能,应把它升级为 manifest 字段、ABI policy、CI 测试或加载器状态检查。

深化切片 4:加载阶段 ​

**工程问题。**使用 RTLD_LOCAL 或平台等价策略减少符号污染;Windows 上记录 GetLastError,Linux 上记录 dlerror。

**最小实验。**建立一个只包含宿主、一个插件、一个 manifest 和一条构建命令的小目录。先让正确路径通过,再故意破坏一个变量,例如版本号、结构体大小、入口符号、权限声明、线程关闭或输出缓冲区。

**观察证据。**记录宿主日志、动态加载错误、返回状态码、插件 id、api version、capabilities 和当前生命周期状态。错误消息应能让维护者知道失败发生在发现、加载、协商、初始化、运行、关闭还是卸载阶段。

text
candidate plugin
  -> check one boundary
  -> inject one failure
  -> record one reproducible proof
1
2
3
4

**复盘问题。**如果这个插件由第三方供应商构建、两年后升级、或在 Windows/Linux 两个平台发布,这条规则是否仍然能保护宿主?如果不能,应把它升级为 manifest 字段、ABI policy、CI 测试或加载器状态检查。

深化切片 5:查询阶段 ​

**工程问题。**只查找一个入口:plugin_query_api_v1。入口缺失说明这不是本宿主支持的插件,不应继续猜其他符号。

**最小实验。**建立一个只包含宿主、一个插件、一个 manifest 和一条构建命令的小目录。先让正确路径通过,再故意破坏一个变量,例如版本号、结构体大小、入口符号、权限声明、线程关闭或输出缓冲区。

**观察证据。**记录宿主日志、动态加载错误、返回状态码、插件 id、api version、capabilities 和当前生命周期状态。错误消息应能让维护者知道失败发生在发现、加载、协商、初始化、运行、关闭还是卸载阶段。

text
candidate plugin
  -> check one boundary
  -> inject one failure
  -> record one reproducible proof
1
2
3
4

**复盘问题。**如果这个插件由第三方供应商构建、两年后升级、或在 Windows/Linux 两个平台发布,这条规则是否仍然能保护宿主?如果不能,应把它升级为 manifest 字段、ABI policy、CI 测试或加载器状态检查。

深化切片 6:协商阶段 ​

**工程问题。**检查 api_version、struct_size、capabilities 和 required host services。失败要能告诉用户是宿主太旧还是插件太旧。

**最小实验。**建立一个只包含宿主、一个插件、一个 manifest 和一条构建命令的小目录。先让正确路径通过,再故意破坏一个变量,例如版本号、结构体大小、入口符号、权限声明、线程关闭或输出缓冲区。

**观察证据。**记录宿主日志、动态加载错误、返回状态码、插件 id、api version、capabilities 和当前生命周期状态。错误消息应能让维护者知道失败发生在发现、加载、协商、初始化、运行、关闭还是卸载阶段。

text
candidate plugin
  -> check one boundary
  -> inject one failure
  -> record one reproducible proof
1
2
3
4

**复盘问题。**如果这个插件由第三方供应商构建、两年后升级、或在 Windows/Linux 两个平台发布,这条规则是否仍然能保护宿主?如果不能,应把它升级为 manifest 字段、ABI policy、CI 测试或加载器状态检查。

深化切片 7:初始化阶段 ​

**工程问题。**init 只能注册资源和读取配置,不应启动不可取消的后台线程。启动线程必须登记到生命周期管理器。

**最小实验。**建立一个只包含宿主、一个插件、一个 manifest 和一条构建命令的小目录。先让正确路径通过,再故意破坏一个变量,例如版本号、结构体大小、入口符号、权限声明、线程关闭或输出缓冲区。

**观察证据。**记录宿主日志、动态加载错误、返回状态码、插件 id、api version、capabilities 和当前生命周期状态。错误消息应能让维护者知道失败发生在发现、加载、协商、初始化、运行、关闭还是卸载阶段。

text
candidate plugin
  -> check one boundary
  -> inject one failure
  -> record one reproducible proof
1
2
3
4

**复盘问题。**如果这个插件由第三方供应商构建、两年后升级、或在 Windows/Linux 两个平台发布,这条规则是否仍然能保护宿主?如果不能,应把它升级为 manifest 字段、ABI policy、CI 测试或加载器状态检查。

深化切片 8:注册阶段 ​

**工程问题。**插件向宿主注册 command、processor、view 或 importer 时,宿主保存的是描述符和函数表,不保存插件内部对象布局。

**最小实验。**建立一个只包含宿主、一个插件、一个 manifest 和一条构建命令的小目录。先让正确路径通过,再故意破坏一个变量,例如版本号、结构体大小、入口符号、权限声明、线程关闭或输出缓冲区。

**观察证据。**记录宿主日志、动态加载错误、返回状态码、插件 id、api version、capabilities 和当前生命周期状态。错误消息应能让维护者知道失败发生在发现、加载、协商、初始化、运行、关闭还是卸载阶段。

text
candidate plugin
  -> check one boundary
  -> inject one failure
  -> record one reproducible proof
1
2
3
4

**复盘问题。**如果这个插件由第三方供应商构建、两年后升级、或在 Windows/Linux 两个平台发布,这条规则是否仍然能保护宿主?如果不能,应把它升级为 manifest 字段、ABI policy、CI 测试或加载器状态检查。

深化切片 9:运行阶段 ​

**工程问题。**每次调用都带 request id、取消令牌或上下文,便于日志关联和 shutdown 时排空。

**最小实验。**建立一个只包含宿主、一个插件、一个 manifest 和一条构建命令的小目录。先让正确路径通过,再故意破坏一个变量,例如版本号、结构体大小、入口符号、权限声明、线程关闭或输出缓冲区。

**观察证据。**记录宿主日志、动态加载错误、返回状态码、插件 id、api version、capabilities 和当前生命周期状态。错误消息应能让维护者知道失败发生在发现、加载、协商、初始化、运行、关闭还是卸载阶段。

text
candidate plugin
  -> check one boundary
  -> inject one failure
  -> record one reproducible proof
1
2
3
4

**复盘问题。**如果这个插件由第三方供应商构建、两年后升级、或在 Windows/Linux 两个平台发布,这条规则是否仍然能保护宿主?如果不能,应把它升级为 manifest 字段、ABI policy、CI 测试或加载器状态检查。

深化切片 10:取消阶段 ​

**工程问题。**取消是请求,不是强杀。插件应周期性检查取消标志,宿主设置 deadline,超时进入隔离或进程外终止。

**最小实验。**建立一个只包含宿主、一个插件、一个 manifest 和一条构建命令的小目录。先让正确路径通过,再故意破坏一个变量,例如版本号、结构体大小、入口符号、权限声明、线程关闭或输出缓冲区。

**观察证据。**记录宿主日志、动态加载错误、返回状态码、插件 id、api version、capabilities 和当前生命周期状态。错误消息应能让维护者知道失败发生在发现、加载、协商、初始化、运行、关闭还是卸载阶段。

text
candidate plugin
  -> check one boundary
  -> inject one failure
  -> record one reproducible proof
1
2
3
4

**复盘问题。**如果这个插件由第三方供应商构建、两年后升级、或在 Windows/Linux 两个平台发布,这条规则是否仍然能保护宿主?如果不能,应把它升级为 manifest 字段、ABI policy、CI 测试或加载器状态检查。

深化切片 11:关闭阶段 ​

**工程问题。**shutdown 后插件不得再调用宿主回调,不得再提交 UI/事件/后台任务。宿主应拒绝 late callback 并记录。

**最小实验。**建立一个只包含宿主、一个插件、一个 manifest 和一条构建命令的小目录。先让正确路径通过,再故意破坏一个变量,例如版本号、结构体大小、入口符号、权限声明、线程关闭或输出缓冲区。

**观察证据。**记录宿主日志、动态加载错误、返回状态码、插件 id、api version、capabilities 和当前生命周期状态。错误消息应能让维护者知道失败发生在发现、加载、协商、初始化、运行、关闭还是卸载阶段。

text
candidate plugin
  -> check one boundary
  -> inject one failure
  -> record one reproducible proof
1
2
3
4

**复盘问题。**如果这个插件由第三方供应商构建、两年后升级、或在 Windows/Linux 两个平台发布,这条规则是否仍然能保护宿主?如果不能,应把它升级为 manifest 字段、ABI policy、CI 测试或加载器状态检查。

深化切片 12:卸载阶段 ​

**工程问题。**引用计数归零、线程停止、回调撤销、资源释放后才能关闭库句柄。Windows 热更新使用版本化文件名避免覆盖已加载 DLL。

**最小实验。**建立一个只包含宿主、一个插件、一个 manifest 和一条构建命令的小目录。先让正确路径通过,再故意破坏一个变量,例如版本号、结构体大小、入口符号、权限声明、线程关闭或输出缓冲区。

**观察证据。**记录宿主日志、动态加载错误、返回状态码、插件 id、api version、capabilities 和当前生命周期状态。错误消息应能让维护者知道失败发生在发现、加载、协商、初始化、运行、关闭还是卸载阶段。

text
candidate plugin
  -> check one boundary
  -> inject one failure
  -> record one reproducible proof
1
2
3
4

**复盘问题。**如果这个插件由第三方供应商构建、两年后升级、或在 Windows/Linux 两个平台发布,这条规则是否仍然能保护宿主?如果不能,应把它升级为 manifest 字段、ABI policy、CI 测试或加载器状态检查。

深化切片 13:回滚阶段 ​

**工程问题。**保留上一版 manifest、库、符号和 hash。新插件加载失败时恢复路由,不让半初始化插件留在 registry。

**最小实验。**建立一个只包含宿主、一个插件、一个 manifest 和一条构建命令的小目录。先让正确路径通过,再故意破坏一个变量,例如版本号、结构体大小、入口符号、权限声明、线程关闭或输出缓冲区。

**观察证据。**记录宿主日志、动态加载错误、返回状态码、插件 id、api version、capabilities 和当前生命周期状态。错误消息应能让维护者知道失败发生在发现、加载、协商、初始化、运行、关闭还是卸载阶段。

text
candidate plugin
  -> check one boundary
  -> inject one failure
  -> record one reproducible proof
1
2
3
4

**复盘问题。**如果这个插件由第三方供应商构建、两年后升级、或在 Windows/Linux 两个平台发布,这条规则是否仍然能保护宿主?如果不能,应把它升级为 manifest 字段、ABI policy、CI 测试或加载器状态检查。

深化切片 14:测试阶段 ​

**工程问题。**测试坏 manifest、缺入口符号、api 过新、struct_size 过小、process 超时、shutdown 失败和卸载后 late callback。

**最小实验。**建立一个只包含宿主、一个插件、一个 manifest 和一条构建命令的小目录。先让正确路径通过,再故意破坏一个变量,例如版本号、结构体大小、入口符号、权限声明、线程关闭或输出缓冲区。

**观察证据。**记录宿主日志、动态加载错误、返回状态码、插件 id、api version、capabilities 和当前生命周期状态。错误消息应能让维护者知道失败发生在发现、加载、协商、初始化、运行、关闭还是卸载阶段。

text
candidate plugin
  -> check one boundary
  -> inject one failure
  -> record one reproducible proof
1
2
3
4

**复盘问题。**如果这个插件由第三方供应商构建、两年后升级、或在 Windows/Linux 两个平台发布,这条规则是否仍然能保护宿主?如果不能,应把它升级为 manifest 字段、ABI policy、CI 测试或加载器状态检查。

深化切片 15:发现阶段 ​

**工程问题。**扫描目录时只收集候选路径,不加载库。先做扩展名、目录白名单、manifest 名称和路径规范化检查。

**最小实验。**建立一个只包含宿主、一个插件、一个 manifest 和一条构建命令的小目录。先让正确路径通过,再故意破坏一个变量,例如版本号、结构体大小、入口符号、权限声明、线程关闭或输出缓冲区。

**观察证据。**记录宿主日志、动态加载错误、返回状态码、插件 id、api version、capabilities 和当前生命周期状态。错误消息应能让维护者知道失败发生在发现、加载、协商、初始化、运行、关闭还是卸载阶段。

text
candidate plugin
  -> check one boundary
  -> inject one failure
  -> record one reproducible proof
1
2
3
4

**复盘问题。**如果这个插件由第三方供应商构建、两年后升级、或在 Windows/Linux 两个平台发布,这条规则是否仍然能保护宿主?如果不能,应把它升级为 manifest 字段、ABI policy、CI 测试或加载器状态检查。

16. 收束检查 ​

  • 是否存在一个唯一、稳定、可查找的 C ABI 入口?
  • 是否在加载前完成 manifest、路径、版本和信任检查?
  • 是否用 api_version、struct_size 和 capabilities 完成协商?
  • 是否明确内存、错误、异常、线程、回调和卸载责任?
  • 是否有坏插件、旧插件、新宿主、取消、shutdown 失败和回滚测试?

最后更新于:

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

持续记录,持续成长

Copyright © Tidenflow