桌面应用做开放扩展,业界有三条成熟路线:沙箱插件(VS Code、浏览器扩展)、脚本沙箱(uTools/ Alfred)、配置驱动(Rainmeter 风格)。本文以 yyzTools 开放的模块 SDK 为第四种样本——声明式宿主 + JS 桥 + 无沙箱信任模型——对比四种模式的架构要素与适用边界,供做桌面端架构、扩展体系设计的读者参考。
一、四种扩展架构模式对比
| 模式 | 代表 | 扩展形态 | 隔离 | 上手成本 | 适用场景 |
|---|---|---|---|---|---|
| 沙箱插件 + 审核市场 | 浏览器扩展、VS Code | 专用 API + 打包上架 | 强(权限声明 + 沙箱) | 高 | 面向陌生开发者的公开生态 |
| 脚本引擎沙箱 | uTools、Alfred | JS 脚本 + 受控 API | 中(能力白名单) | 中 | 个人效率工具的轻扩展 |
| 纯配置驱动 | Rainmeter、Listary | 数据文件声明命令 | 无代码 | 极低 | 「快速打开某物」类需求 |
| 声明式宿主 + JS 桥 | yyzTools SDK | JSON 声明窗口 + 静态网页 | 弱(信任模型,非沙箱) | 低(会网页即会) | 开发者给自己/小团队写工具 |
yyzTools 的定位处在光谱中间偏配置端:命令级扩展用纯 JSON(.zenmod type:"app",加条命令零代码),页面级扩展用「一个 JSON 声明 + 一个静态网页」(完整工具窗口,零 C++ 零构建)。本文聚焦后者。
二、声明式宿主:窗口即数据
扩展体系最重的架构负担是窗口与集成。yyzTools 的解法是把宿主整体数据化:
{
"id": "sdk_sample",
"type": "web",
"url": "SDK_Sample/index.html",
"width": 760, "height": 540,
"layoutType": 34
}
主程序扫描 .zenmod,type:"web" 自动创建「原生窗口 + WebView2」宿主。架构上的收益:
- 集成分摊:命令面板搜索(含拼音索引)、全局快捷键、程序坞挂载、窗口控制名称表,全部由宿主层统一提供——扩展不写一行集成代码就继承整个工具集的入口体系;
- 生命周期统一管理:
preCreate(预创建提速)、canSuspend(隐藏时挂起 WebView 省资源)这类策略在宿主层实现,扩展作者无感知; - 主题通道:宿主注入
--zen-*CSS 变量并回推__applyTheme回调,扩展自动跟随主程序深浅色。
代价是窗口形态被宿主能力圈定(自定义非标窗口做不了),这是「95% 场景收益 vs 5% 场景自由度」的显式取舍。
三、JS 桥的分层设计
扩展与系统的唯一通道是 window.Zen,桥接分三层:
扩展页面 ── ZenAPI 封装类(官方 JS 库)
└─ window.Zen.xxx(...) ← WebView2 桥
└─ C++ Manager 层(BindSync / BindAsync 注册表)
两个架构决策值得展开:
调用规范固化在封装层。接口返回 { error, ... } 契约,但序列化路径不同字段类型有差异(ptree 路径布尔变字符串、部分接口 isDir 是 1/0 数字)——封装层统一提供 isOk() 判错,把类型坑从「每个扩展作者都要踩」收敛为「封装层一次性处理」。桥接层的类型语义必须由官方封装兜底,这是 JS 桥设计的铁律。
异步绑定的线程模型。耗时接口(全盘搜索 searchFile、批量应用信息 getAppInfo、OCR)走 BindAsync 后台线程执行;且搜索采用槽机制——新请求顶掉未完成旧请求而非排队。人机交互场景下连续请求的正确语义是覆盖:用户击键时只有最后一次有意义,排队会让后台做一堆立刻作废的全表扫描。前端配 300ms 防抖 + 递增序号丢弃乱序回包,双层防护。
四、无沙箱的信任模型
这个 SDK 不设权限弹窗、不做沙箱隔离、无撤销机制——Native API 完整开放文件、进程、剪贴板能力。
架构上这是成立的,因为信任模型不同:扩展的部署单位是「复制目录」,分发半径是「开发者自己的机器或团队」——攻击者若能写入你的模块目录,早已具备直接投放可执行文件的权限,伪装成模块没有增益。沙箱保护的是「陌生分发」场景,而那不是这个体系的目标场景(若未来开模块商店,信任模型改变,沙箱与审核就必须补上)。
文档如实声明了这一点:调用前自行评估、校验外部输入、不可逆操作先确认。安全设计应匹配信任模型,而不是无差别堆砌隔离层——隔离的每一层都是扩展作者的心智成本。
五、工程细节两则
- 免构建即官方形态:样例零依赖零构建,双击 index.html 可在浏览器调试(无
window.Zen时封装层走异常路径返回error:-1,渲染逻辑照常可调)。浏览器可调试性是「网页扩展」相对原生插件被低估的优势——宿主能力缺失时的降级路径在设计之初就留好了。 - 结果自带图标:
searchFile返回项直接带 base64icon字段,<img src>直饮,免去每个结果一次getFileIconIPC。高频路径的字段冗余是正向设计——接口设计里可以藏性能。
六、小结
| 设计题 | 本样本的答案 |
|---|---|
| 扩展怎么声明窗口 | 数据驱动(.zenmod JSON → 宿主自动创建) |
| 集成成本谁担 | 宿主层统一分摊(搜索/快捷键/程序坞/主题) |
| 桥接类型语义 | 官方封装层兜底(isOk 归一) |
| 并发请求语义 | 覆盖而非排队(交互场景槽机制) |
| 安全 | 匹配信任模型,不做无差别沙箱 |
SDK 文档(12 语)与免构建样例在官网开发者页 yyztools.com/sdk.html。工具集 v1.0.5,Windows 10/11,永久免费。样例用的全盘搜索接口正是 1.0.5 刚替换的自研 MFT 引擎——扩展作者第一天就能摸到最新底层能力。