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)
16 KiB
16 KiB
插件系统设计
v6.0 引入插件系统,允许第三方开发者扩展 ViewDesignEngine 的功能,无需修改核心代码。
目录
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(抽象基类)
所有插件必须实现的接口:
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(插件元数据)
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(插件管理器)
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 链接符号:
// 创建插件实例(必须导出)
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 最小插件示例
// 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 构建
# 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
│
└── 返回加载成功数量
版本兼容性检查
// 伪代码
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 描述文件,加快发现速度:
{
"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 加载优先级
- 如果存在
plugin.json→ 先读取 JSON 验证兼容性 - 兼容 →
dlopen+dlsym(create_plugin) - 不兼容 → 跳过,记录日志
- 无
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 最佳安全实践
- 始终验证 manifest 版本兼容性后再 initialize
- dlopen 使用
RTLD_NOW | RTLD_LOCAL而非RTLD_LAZY | RTLD_GLOBAL - 设置超时:execute() 调用应有超时机制
- 资源限制:通过 cgroups/Docker 限制插件资源
- 日志审计:记录所有插件加载/卸载/执行事件
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 使用示例
#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) |