# 插件系统设计 v6.0 引入插件系统,允许第三方开发者扩展 ViewDesignEngine 的功能,无需修改核心代码。 ## 目录 1. [设计目标](#1-设计目标) 2. [架构概览](#2-架构概览) 3. [核心组件](#3-核心组件) 4. [插件开发](#4-插件开发) 5. [插件加载流程](#5-插件加载流程) 6. [生命周期管理](#6-生命周期管理) 7. [清单文件](#7-清单文件) 8. [安全考虑](#8-安全考虑) 9. [最佳实践](#9-最佳实践) --- ## 1. 设计目标 | 目标 | 说明 | |------|------| | **零侵入** | 插件不修改核心代码,独立编译 | | **动态加载** | 运行时 dlopen 加载,无需重新编译 VDE | | **版本兼容** | Manifest 声明版本约束,自动检查兼容性 | | **隔离性** | 插件崩溃不影响主程序 | | **易开发** | 继承 `PluginInterface` 即可,最少样板代码 | ## 2. 架构概览 ``` ┌─────────────────────────────────────────────────────────────┐ │ Application │ │ │ │ │ PluginManager │ │ ┌──────┴──────┐ │ │ │ Registry │ │ │ │ name→Plugin* │ │ │ └──────┬──────┘ │ │ ┌───────────────┼───────────────┐ │ │ │ │ │ │ │ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │ │ │Plugin A │ │Plugin B │ │Plugin C │ │ │ │(.so) │ │(.so) │ │(.so) │ │ │ │IO Format│ │Geometry │ │ Custom │ │ │ │Exporter │ │Filter │ │ Tool │ │ │ └─────────┘ └─────────┘ └─────────┘ │ │ │ │ Plugin Directory: /usr/lib/vde/plugins/ │ │ ./vde_plugins/ │ │ $VDE_PLUGIN_PATH │ └─────────────────────────────────────────────────────────────┘ ``` ## 3. 核心组件 ### 3.1 PluginInterface(抽象基类) 所有插件必须实现的接口: ```cpp namespace vde::plugin { class PluginInterface { public: virtual ~PluginInterface() = default; /// 插件初始化(加载后调用一次) virtual bool initialize() = 0; /// 插件清理(卸载前调用一次) virtual void shutdown() = 0; /// 返回插件清单 virtual const PluginManifest& manifest() const = 0; /// 插件唯一名称(如 "io.export.usdz") virtual const char* name() const = 0; /// 插件版本(语义化版本) virtual const char* version() const = 0; /// 执行插件功能 virtual bool execute(const std::string& action, const std::map& params, void* input, void* output) = 0; }; } // namespace vde::plugin ``` ### 3.2 PluginManifest(插件元数据) ```cpp namespace vde::plugin { struct PluginManifest { std::string name; ///< 唯一标识符,如 "io.export.usdz" std::string version; ///< 语义化版本 "1.0.0" std::string author; ///< 作者信息 std::string description; ///< 功能描述 std::string license; ///< 许可证 "MIT" / "Apache-2.0" std::string vde_version_min;///< 最低 VDE 版本 "6.0.0" std::string vde_version_max;///< 最高兼容 VDE 版本 (空=无上限) /// 插件类型 enum class Type { IO_IMPORT, ///< 格式导入器 IO_EXPORT, ///< 格式导出器 GEOMETRY_FILTER,///< 几何过滤器 CUSTOM_TOOL, ///< 自定义工具 OTHER }; Type type = Type::OTHER; /// 依赖的其他插件名称(可选) std::vector dependencies; /// 从 JSON 文件加载 static std::optional from_file(const std::string& path); }; } // namespace vde::plugin ``` ### 3.3 PluginManager(插件管理器) ```cpp namespace vde::plugin { class PluginManager { public: PluginManager(); ~PluginManager(); // 禁止拷贝 PluginManager(const PluginManager&) = delete; PluginManager& operator=(const PluginManager&) = delete; /// 从目录加载所有插件 int load_plugin_dir(const std::string& dir_path); /// 加载单个插件(路径到 .so/.dylib/.dll) bool load_plugin(const std::string& plugin_path); /// 卸载指定插件 bool unload_plugin(const std::string& name); /// 卸载所有插件 void unload_all(); /// 获取已加载的插件 PluginInterface* get_plugin(const std::string& name); /// 列出所有已加载插件 std::vector list_plugins() const; /// 检查插件是否已加载 bool is_loaded(const std::string& name) const; /// 获取加载错误信息 std::string last_error() const; private: struct Impl; std::unique_ptr impl_; }; } // namespace vde::plugin ``` ### 3.4 注册/加载函数 每个插件动态库必须导出以下 C 链接符号: ```cpp // 创建插件实例(必须导出) extern "C" VDE_PLUGIN_EXPORT vde::plugin::PluginInterface* create_plugin(); // 销毁插件实例(必须导出) extern "C" VDE_PLUGIN_EXPORT void destroy_plugin(vde::plugin::PluginInterface* plugin); // 获取插件清单(可选,优化加载速度) extern "C" VDE_PLUGIN_EXPORT const vde::plugin::PluginManifest* get_manifest(); ``` ## 4. 插件开发 ### 4.1 最小插件示例 ```cpp // my_exporter.cpp #include #include using namespace vde::plugin; class MyExporter : public PluginInterface { public: bool initialize() override { manifest_.name = "io.export.myformat"; manifest_.version = "1.0.0"; manifest_.author = "Your Name"; manifest_.description = "Export to MyFormat"; manifest_.type = PluginManifest::Type::IO_EXPORT; manifest_.vde_version_min = "6.0.0"; return true; } void shutdown() override { // 清理资源 } const PluginManifest& manifest() const override { return manifest_; } const char* name() const override { return manifest_.name.c_str(); } const char* version() const override { return manifest_.version.c_str(); } bool execute(const std::string& action, const std::map& params, void* input, void* output) override { if (action == "export") { // 执行导出逻辑 return true; } return false; } private: PluginManifest manifest_; }; // 必需的导出符号 extern "C" VDE_PLUGIN_EXPORT PluginInterface* create_plugin() { return new MyExporter(); } extern "C" VDE_PLUGIN_EXPORT void destroy_plugin(PluginInterface* plugin) { delete plugin; } ``` ### 4.2 CMake 构建 ```cmake # CMakeLists.txt for plugin cmake_minimum_required(VERSION 3.16) project(my_exporter VERSION 1.0.0) find_package(ViewDesignEngine REQUIRED) add_library(my_exporter SHARED my_exporter.cpp) target_link_libraries(my_exporter PRIVATE vde::engine) target_include_directories(my_exporter PRIVATE ${VDE_INCLUDE_DIRS}) # 设置输出到插件目录 set_target_properties(my_exporter PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins" ) ``` ### 4.3 插件类型 | 类型 | 枚举值 | 说明 | 示例 | |------|--------|------|------| | 格式导入器 | `IO_IMPORT` | 新增文件格式导入 | USDZ, 3DS, AMF | | 格式导出器 | `IO_EXPORT` | 新增文件格式导出 | USDZ, FBX, X3D | | 几何过滤器 | `GEOMETRY_FILTER` | 几何后处理 | 平滑、简化、重网格 | | 自定义工具 | `CUSTOM_TOOL` | 领域特定工具 | 应力分析、拓扑优化 | ## 5. 插件加载流程 ``` Application::init() │ ▼ PluginManager::load_plugin_dir("/usr/lib/vde/plugins") │ ├── 扫描目录: for each *.so / *.dylib / *.dll │ ├── dlopen(plugin_path, RTLD_NOW) │ │ │ ├── 成功 → 继续 │ └── 失败 → 记录错误,跳过 │ ├── dlsym(handle, "get_manifest") │ │ │ ├── 找到 → 读取 manifest,检查版本兼容性 │ └── 未找到 → 继续加载(稍后从 initialize 获取) │ ├── dlsym(handle, "create_plugin") │ │ │ ├── 找到 → PluginInterface* plugin = create_plugin() │ └── 未找到 → dlclose,跳过 │ ├── plugin->initialize() │ │ │ ├── 返回 true → 注册到 Registry │ └── 返回 false → destroy_plugin + dlclose │ └── 返回加载成功数量 ``` ### 版本兼容性检查 ```cpp // 伪代码 bool is_compatible(const PluginManifest& m, const std::string& vde_version) { // 检查最小版本 if (compare_versions(vde_version, m.vde_version_min) < 0) return false; // 检查最大版本(如果指定) if (!m.vde_version_max.empty() && compare_versions(vde_version, m.vde_version_max) > 0) return false; return true; } ``` ## 6. 生命周期管理 ``` ┌──────────────────────────────────────────────────────────────┐ │ 插件生命周期 │ │ │ │ [编译] → [发现] → [加载] → [初始化] → [运行] → [卸载] │ │ │ │ 编译: 开发者编写代码,构建为 .so/.dylib/.dll │ │ 发现: PluginManager 扫描插件目录 │ │ 加载: dlopen 加载动态库 │ │ 初始化: initialize() 注册功能 │ │ 运行: execute() 被调用 │ │ 卸载: shutdown() + dlclose │ │ │ │ 状态机: │ │ UNLOADED → LOADING → INITIALIZED → RUNNING │ │ ↑ ↓ ↓ │ │ └── ERROR SHUTTING_DOWN → UNLOADED│ └──────────────────────────────────────────────────────────────┘ ``` ### 状态说明 | 状态 | 说明 | |------|------| | `UNLOADED` | 插件未加载或已卸载 | | `LOADING` | dlopen 成功,正在初始化 | | `INITIALIZED` | initialize() 完成,等待 execute | | `RUNNING` | execute() 正在执行中 | | `SHUTTING_DOWN` | shutdown() 执行中 | | `ERROR` | 加载或初始化失败 | ## 7. 清单文件 插件可以在同目录放置 `plugin.json` 描述文件,加快发现速度: ```json { "name": "io.export.usdz", "version": "1.0.0", "author": "Your Name ", "description": "Export geometry to USDZ format for Apple AR", "license": "MIT", "type": "IO_EXPORT", "vde_version_min": "6.0.0", "vde_version_max": "", "dependencies": [], "library": "libvde_usdz_export.so", "entry_points": { "create": "create_plugin", "destroy": "destroy_plugin" } } ``` ### PluginManager 加载优先级 1. 如果存在 `plugin.json` → 先读取 JSON 验证兼容性 2. 兼容 → `dlopen` + `dlsym(create_plugin)` 3. 不兼容 → 跳过,记录日志 4. 无 `plugin.json` → 直接 `dlopen` + 尝试 `get_manifest` 符号 ## 8. 安全考虑 ### 8.1 隔离性 | 措施 | 说明 | |------|------| | 独立 .so | 每个插件独立动态库 | | 崩溃隔离 | 插件 SIGSEGV 不传播到主程序(信号处理器) | | 符号可见性 | 插件使用 `-fvisibility=hidden` 隐藏内部符号 | | 内存隔离 | 插件通过 PluginInterface 与主程序交互,不直接访问内部数据 | ### 8.2 信任模型 ``` ┌───────────────────────────────────────┐ │ 插件信任级别 │ │ │ │ L0 内置: VDE 官方维护,完全信任 │ │ L1 签名: 第三方已签名,受信任 │ │ L2 社区: 社区插件,有限沙箱 │ │ L3 未知: 未签名/未知来源,严格沙箱 │ │ │ │ L3 限制: │ │ - 不能访问文件系统(除了输出目录) │ │ - 不能调用网络 │ │ - 不能 fork │ │ - CPU/内存限制 │ └───────────────────────────────────────┘ ``` ### 8.3 最佳安全实践 1. **始终验证 manifest 版本兼容性**后再 initialize 2. **dlopen 使用 `RTLD_NOW | RTLD_LOCAL`** 而非 `RTLD_LAZY | RTLD_GLOBAL` 3. **设置超时**:execute() 调用应有超时机制 4. **资源限制**:通过 cgroups/Docker 限制插件资源 5. **日志审计**:记录所有插件加载/卸载/执行事件 ## 9. 最佳实践 ### 9.1 插件开发建议 - ✅ 插件尽量无状态,每次 execute 独立 - ✅ 用 manifest 声明清晰的功能范围 - ✅ 支持版本协商(`vde_version_min` / `vde_version_max`) - ✅ 提供详细的错误信息(通过返回值 + 日志) - ✅ 清理所有资源在 shutdown() 中 - ❌ 不要在 initialize() 中做耗时操作 - ❌ 不要依赖全局可变状态 - ❌ 不要修改 VDE 核心数据结构 ### 9.2 插件命名规范 ``` 格式: .. 示例: - io.import.fbx # FBX 格式导入 - io.export.usdz # USDZ 格式导出 - geometry.smooth.laplacian # Laplacian 平滑 - tool.analysis.stress # 应力分析工具 ``` ### 9.3 使用示例 ```cpp #include int main() { vde::plugin::PluginManager mgr; // 加载所有插件 int count = mgr.load_plugin_dir("/usr/lib/vde/plugins"); std::cout << "Loaded " << count << " plugins\n"; // 列出已加载插件 for (auto& name : mgr.list_plugins()) { std::cout << " - " << name << "\n"; } // 使用特定插件 auto* exporter = mgr.get_plugin("io.export.gltf"); if (exporter) { std::map params; params["path"] = "output.glb"; params["binary"] = "true"; void* mesh_data = /* ... */; bool ok = exporter->execute("export", params, mesh_data, nullptr); } // 卸载 mgr.unload_all(); return 0; } ``` --- ## 附录: 平台差异 | 操作 | Linux | macOS | Windows | |------|-------|-------|---------| | 动态库后缀 | `.so` | `.dylib` | `.dll` | | 加载函数 | `dlopen` | `dlopen` | `LoadLibraryA` | | 符号解析 | `dlsym` | `dlsym` | `GetProcAddress` | | 卸载函数 | `dlclose` | `dlclose` | `FreeLibrary` | | 错误信息 | `dlerror()` | `dlerror()` | `GetLastError()` | | 链接库 | `-ldl` | (内置) | `kernel32.lib` | | 可见性宏 | `__attribute__((visibility("default")))` | 同 Linux | `__declspec(dllexport)` |