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、函数表与受控 C++ 接口 / Plugin Interface Design with C ABI, Function Tables, and Controlled C++ Interfaces ​

1. 为什么接口边界比类设计更重要 ​

插件系统最容易犯的错误,是把“我在同一个工程里能调用一个 C++ 对象”误认为“这个对象可以作为长期插件 ABI”。

宿主和插件不是普通的两个源文件。它们可能由不同编译器、不同标准库、不同运行库、不同优化选项、不同异常模型、不同分配器构建。只要其中一个条件变化,C++ 类布局、vtable、RTTI、异常和 STL 类型都可能变成风险。

所以插件接口设计的第一原则是:

text
public plugin boundary should be smaller and more stable than internal C++ code
1

插件内部当然可以使用现代 C++、模板、STL、继承、异常、协程、Qt 或第三方库。问题只在边界:哪些数据和函数跨过 DLL/SO 边界。

2. 最简单心智模型 ​

text
host process
  |
  | load library
  | find exported C symbol
  v
plugin_query_api_v1()
  |
  v
versioned function table
  |
  +-- init(ctx)
  +-- register_capabilities(registry)
  +-- process(request, response)
  +-- cancel(token)
  +-- shutdown()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

这个模型故意不把 C++ 对象指针作为第一等边界。宿主先拿到一个 C 符号,再通过结构体大小、API 版本和能力位协商,最后调用函数表。函数表背后可以是 C++ 对象,但宿主不需要知道对象布局。

3. 核心术语 ​

C ABI:用 C 语言调用约定、符号名和简单数据布局表达的二进制接口。它不是绝对跨平台魔法,但比 C++ 类 ABI 更容易控制。

函数表:一个结构体,里面放函数指针。它相当于手写 vtable,但字段顺序、结构体大小和版本由我们显式管理。

struct_size:结构体第一个常见字段,表示调用方传入或插件返回的结构体大小。它允许新版本在尾部添加字段,同时老版本仍能识别已知部分。

能力位:插件声明自己支持哪些可选功能,例如 streaming、cancel、hot_reload、gpu_acceleration。能力位比“猜函数是否为空”更利于诊断。

受控 C++ 接口:宿主和插件使用同一 ABI 家族、同一编译器策略、同一运行库约束,并明确禁止跨边界 STL/异常/分配器时,才考虑 C++ 纯虚接口。

4. 三种接口层级 ​

层级形式适合场景风险
裸函数每个函数单独 dlsym/GetProcAddress很小 demo 或单函数扩展无元数据、无版本协商、调用点分散
C ABI 函数表一个导出入口返回版本化函数表默认推荐的跨编译器插件边界需要手写结构和错误码
C++ 纯虚接口extern "C" 工厂返回接口指针受控工具链内部生态vtable、异常、RTTI、分配器、STL 风险

注意:COM 不是“任意 C++ 纯虚类”。COM 依赖固定调用约定、IUnknown、QueryInterface、GUID、AddRef/Release 引用计数和平台 ABI 规范。把普通 C++ 抽象类叫成 COM 风格,会漏掉最关键的契约。

5. 推荐的 C ABI 函数表示例 ​

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

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

enum plugin_status {
    PLUGIN_OK = 0,
    PLUGIN_ERROR_INVALID_ARGUMENT = 1,
    PLUGIN_ERROR_UNSUPPORTED_VERSION = 2,
    PLUGIN_ERROR_INTERNAL = 3
};

struct 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 plugin_descriptor_v1 {
    uint32_t api_version;
    size_t struct_size;
    const char* id;
    const char* name;
    const char* version;
    uint64_t capabilities;
};

struct plugin_api_v1 {
    uint32_t api_version;
    size_t struct_size;
    plugin_status (*init)(const host_services_v1* host);
    plugin_status (*describe)(plugin_descriptor_v1* out);
    plugin_status (*process)(const char* input, char* output, size_t output_size);
    plugin_status (*shutdown)();
};

PLUGIN_EXPORT plugin_status plugin_query_api_v1(
    const host_services_v1* host,
    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

这里有几个故意设计:

  • 入口函数是 C 符号,宿主能稳定查找;
  • 结构体带 api_version 和 struct_size;
  • 字符串跨边界只使用 const char* 或宿主提供的缓冲区;
  • 分配函数由宿主服务表提供时,释放责任也清楚;
  • 所有函数返回错误码,不抛 C++ 异常过边界。

6. 宿主加载顺序 ​

text
discover candidate file
  -> read manifest before loading
  -> check signature/hash/path policy
  -> load library with local symbols
  -> find plugin_query_api_v1
  -> pass HostServices
  -> check api_version/struct_size/capabilities
  -> init
  -> register commands or processors
  -> run
  -> shutdown
  -> unload only after references drain
1
2
3
4
5
6
7
8
9
10
11
12

manifest 预校验很重要。宿主不应该先加载任意 DLL/SO 再问“你是谁”。加载本身就可能执行运行时初始化代码,因此安全系统至少要在加载前检查路径、签名、hash、插件 id、版本和权限声明。

7. C++ 纯虚接口什么时候可以用 ​

C++ 纯虚接口可以在下面条件同时满足时作为工程折中:

  1. 宿主和插件由同一编译器 ABI 家族构建;
  2. 发布矩阵固定运行库、标准库、异常和 RTTI 策略;
  3. 接口类没有数据成员,尽量没有多重继承;
  4. 不在接口参数或返回值中出现 STL、异常对象、智能指针或跨模块分配对象;
  5. 对象必须由创建它的模块销毁;
  6. 每次接口变更都进入 ABI 兼容审查和旧插件测试。

这不是“跨编译器安全”,而是“受控环境下可维护”。如果插件生态面向第三方供应商、未知编译器或长期二进制兼容,默认选择 C ABI 函数表。

8. 错误与异常边界 ​

插件内部可以抛异常,但必须在边界内捕获:

cpp
static plugin_status process_impl(const char* input, char* output, size_t output_size) {
    try {
        if (!input || !output || output_size == 0) {
            return PLUGIN_ERROR_INVALID_ARGUMENT;
        }
        // internal C++ code may use std::string, exceptions, libraries
        return PLUGIN_OK;
    } catch (...) {
        return PLUGIN_ERROR_INTERNAL;
    }
}
1
2
3
4
5
6
7
8
9
10
11

不要让异常穿过宿主和插件边界。不同编译器、运行库和优化配置下,异常展开、RTTI 匹配和析构顺序都可能不同。

9. 所有权规则 ​

text
host allocated memory   -> host frees
plugin allocated memory -> plugin frees
shared output buffer    -> allocator owner is explicit
1
2
3

最简单的做法是宿主提供输出缓冲区,插件写入并返回状态。大对象可以使用句柄:宿主只拿到 opaque handle,再通过 destroy 函数释放。

10. 版本演进策略 ​

函数表最适合尾部扩展:

cpp
struct plugin_api_v2 {
    uint32_t api_version;
    size_t struct_size;
    plugin_status (*init)(const host_services_v1*);
    plugin_status (*describe)(plugin_descriptor_v1*);
    plugin_status (*process)(const char*, char*, size_t);
    plugin_status (*shutdown)();
    plugin_status (*cancel)(uint64_t request_id); // v2 appended
};
1
2
3
4
5
6
7
8
9

老宿主看到 struct_size 只读自己知道的字段;新宿主发现 size 不足,就知道 cancel 不可用。不要在中间插字段,也不要改变已有字段语义。

11. 常见错误 ​

  • 把 extern "C" 工厂函数返回 C++ 对象指针,说成跨编译器安全;
  • 接口中传递 std::string、std::vector、异常、智能指针;
  • 插件 new,宿主 delete;
  • 没有 api_version 和 struct_size,导致无法平滑演进;
  • 加载后才检查插件是否可信;
  • unload 前没有停止线程、撤销回调、排空引用;
  • 把 COM 简化为“纯虚类”,漏掉 GUID、引用计数和调用约定。

12. 练习 ​

  1. 把一个 IPlugin* create() 接口改成 plugin_query_api_v1() 函数表。
  2. 给函数表新增 cancel 能力,要求老宿主仍能加载老插件。
  3. 故意让插件返回过大的 struct_size,宿主应拒绝并打印清晰错误。
  4. 写一个 manifest,加载前检查插件 id、api version、sha256 和 capabilities。
  5. 在插件内部抛异常,确认边界函数捕获并返回错误码。

13. 总结 ​

插件接口设计的核心,是把边界做小、做显式、做可协商。C ABI 函数表不是最优雅的写法,却是最容易跨编译器、跨供应商、跨版本长期维护的写法。C++ 纯虚接口可以用,但只能在受控 ABI 环境里用,并且必须接受 ABI policy、兼容矩阵和旧插件测试约束。

返回插件工程路线

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

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

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

深化切片 1:ABI policy 模板 ​

**工程问题。**把插件接口分成 stable、experimental、internal 三类。stable 字段只能尾部扩展;experimental 需要能力位保护;internal 不允许第三方依赖。

**最小实验。**建立一个只包含宿主、一个插件、一个 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:HostServices 设计 ​

**工程问题。**宿主服务表只放跨边界必需能力,例如日志、内存、注册、时间、取消检查。不要把整个宿主 C++ 对象指针塞给插件。

**最小实验。**建立一个只包含宿主、一个插件、一个 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:错误码设计 ​

**工程问题。**错误码要稳定、可记录、可翻译。插件内部异常应转换成错误码,并通过 last_error 或诊断回调提供细节。

**最小实验。**建立一个只包含宿主、一个插件、一个 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:字符串与缓冲区 ​

**工程问题。*输入可用 const char 加长度,输出优先由宿主提供缓冲区;需要动态输出时必须明确 allocator owner。

**最小实验。**建立一个只包含宿主、一个插件、一个 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:能力协商 ​

**工程问题。**能力位用于表达可选功能,宿主不能只因为函数指针非空就默认可用;能力、版本和 struct_size 要一起检查。

**最小实验。**建立一个只包含宿主、一个插件、一个 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:COM 对比 ​

**工程问题。**COM 的稳定来自 IUnknown、GUID、QueryInterface、AddRef/Release 和调用约定,不是来自普通 C++ 纯虚类本身。

**最小实验。**建立一个只包含宿主、一个插件、一个 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:C++ 接口适用条件 ​

**工程问题。**同一编译器 ABI、同一运行库、固定异常/RTTI 策略、禁止 STL 过边界、旧插件测试都满足时,才考虑纯虚接口。

**最小实验。**建立一个只包含宿主、一个插件、一个 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:接口扩展策略 ​

**工程问题。**优先尾部新增函数表字段或新增 query_api_v2;不要改变已有字段含义,不要在结构体中间插字段。

**最小实验。**建立一个只包含宿主、一个插件、一个 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:卸载前置条件 ​

**工程问题。**接口设计阶段就要决定 shutdown、cancel、release、callback revoke 的顺序,否则加载器只能靠约定猜。

**最小实验。**建立一个只包含宿主、一个插件、一个 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:最小反例 ​

**工程问题。**写一个用 std::string 做返回值的接口,再用不同运行库构建宿主和插件,观察崩溃或内存破坏风险。

**最小实验。**建立一个只包含宿主、一个插件、一个 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:文档化 ​

**工程问题。**接口头文件旁边应放 ABI policy、版本历史、字段扩展规则、错误码表和所有权表。

**最小实验。**建立一个只包含宿主、一个插件、一个 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:审查口径 ​

**工程问题。**code review 不只看函数好不好用,还要看每个字段能否跨编译器、跨版本、跨进程诊断。

**最小实验。**建立一个只包含宿主、一个插件、一个 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:ABI policy 模板 ​

**工程问题。**把插件接口分成 stable、experimental、internal 三类。stable 字段只能尾部扩展;experimental 需要能力位保护;internal 不允许第三方依赖。

**最小实验。**建立一个只包含宿主、一个插件、一个 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:HostServices 设计 ​

**工程问题。**宿主服务表只放跨边界必需能力,例如日志、内存、注册、时间、取消检查。不要把整个宿主 C++ 对象指针塞给插件。

**最小实验。**建立一个只包含宿主、一个插件、一个 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:错误码设计 ​

**工程问题。**错误码要稳定、可记录、可翻译。插件内部异常应转换成错误码,并通过 last_error 或诊断回调提供细节。

**最小实验。**建立一个只包含宿主、一个插件、一个 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:字符串与缓冲区 ​

**工程问题。*输入可用 const char 加长度,输出优先由宿主提供缓冲区;需要动态输出时必须明确 allocator owner。

**最小实验。**建立一个只包含宿主、一个插件、一个 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 测试或加载器状态检查。

深化切片 17:能力协商 ​

**工程问题。**能力位用于表达可选功能,宿主不能只因为函数指针非空就默认可用;能力、版本和 struct_size 要一起检查。

**最小实验。**建立一个只包含宿主、一个插件、一个 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 测试或加载器状态检查。

深化切片 18:COM 对比 ​

**工程问题。**COM 的稳定来自 IUnknown、GUID、QueryInterface、AddRef/Release 和调用约定,不是来自普通 C++ 纯虚类本身。

**最小实验。**建立一个只包含宿主、一个插件、一个 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
上一篇2. 运行时动态加载:LoadLibrary 与 dlopen —— 打开插件的大门 / Runtime Dynamic Loading with LoadLibrary and Dlopen
下一篇4. CMake 构建完整插件项目 / Building a Complete Plugin Project with CMake

持续记录,持续成长

Copyright © Tidenflow