5e2812a6f3
v6.2.1 — Blender Integration Addon: - 5 files, 1,732 lines: __init__, operators (9 ops), panels (4 panels) - preferences, vde_bridge (subprocess CLI bridge) - Import/Export STEP, primitives, boolean, SDF→Mesh v6.2.2 — .NET/C# Bindings (VdeSharp): - 8 files: NativeMethods (50+ P/Invoke), BrepModel, MeshData, SdfEngine, Assembly - C API extended with 20 new functions (STEP I/O, Boolean, SDF, Assembly) - vde_capi rebuilt as SHARED library - Example: import STEP → boolean → export v6.2.3 — Developer Docs + Plugin System: - 7 docs: architecture, contributing, API overview, building, testing, plugin-system - plugin_system.h/.cpp: PluginInterface, PluginManager, dlopen/LoadLibrary - README.md updated with v6 features - Syntax-check passed (GCC 10.2.1, -Wall -Wextra -Wpedantic)
502 lines
16 KiB
Markdown
502 lines
16 KiB
Markdown
# 插件系统设计
|
||
|
||
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)` |
|