Files
ViewDesignEngine/docs/dev-guide/plugin-system.md
T

502 lines
16 KiB
Markdown
Raw Normal View History

# 插件系统设计
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<std::string, std::string>& 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<std::string> dependencies;
/// 从 JSON 文件加载
static std::optional<PluginManifest> 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<std::string> list_plugins() const;
/// 检查插件是否已加载
bool is_loaded(const std::string& name) const;
/// 获取加载错误信息
std::string last_error() const;
private:
struct Impl;
std::unique_ptr<Impl> 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 <vde/plugin/plugin_system.h>
#include <iostream>
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<std::string, std::string>& 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 <email@example.com>",
"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 插件命名规范
```
格式: <category>.<subcategory>.<name>
示例:
- io.import.fbx # FBX 格式导入
- io.export.usdz # USDZ 格式导出
- geometry.smooth.laplacian # Laplacian 平滑
- tool.analysis.stress # 应力分析工具
```
### 9.3 使用示例
```cpp
#include <vde/plugin/plugin_system.h>
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<std::string, std::string> 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)` |