-
Notifications
You must be signed in to change notification settings - Fork 1
Debugging Security Compatibility
Ling edited this page Jul 7, 2026
·
4 revisions
插件运行在启动器进程内。调试时既要关注插件自身问题,也要避免影响宿主稳定性。
推荐流程:
- 单独构建插件项目。
- 复制插件包到 PCL 插件目录。
- 启动 PCL。
- 在 IDE 中附加到 PCL 进程。
- 在插件入口或注册的回调中打断点。
日志:
var logger = context.Host.Core.GetLogger("main");
logger.Debug("Debug message");
logger.Info("Plugin loaded");
logger.Warn("Something looks suspicious");
logger.Error("Operation failed", exception);提示:
context.Host.Core.Hint("Saved", PluginHintType.Success);
context.Host.Core.Hint("Failed", PluginHintType.Error);JS 插件没有 Node.js 调试器集成,优先使用日志和 toast:
function load(ctx) {
ctx.log("loading");
ctx.toast("loaded");
}JS 运行时异常会被宿主记录。修改 main.js 后通常需要重启 PCL 重新加载插件。
可能原因:
- manifest 没声明
ContributeTools、ContributeSettings等 UI 能力。 - 插件加载时机太早,UI 尚未就绪。
解决:
"capabilities": ["ContributeTools"]并使用:
LoadTiming = PluginLoadTiming.WindowCreatedmanifest 没声明:
"capabilities": ["RegisterExtension"]检查:
- DLL 中是否有类型实现
IPclPlugin。 - 入口类型是否 public。
- 是否存在公共无参构造函数。
- 插件包是否误带了
PCL.Plugin.Abstractions.dll。 -
plugin.json的entry是否指向正确 DLL。
SDK 包目标框架是 net8.0-windows。插件项目也应使用:
<TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<EnableWindowsTargeting>true</EnableWindowsTargeting>普通 net8.0 项目不能引用该 WPF/Windows SDK 包。
DLL 插件不是安全沙箱。它运行在宿主进程内,理论上可以访问当前进程权限下的文件、网络和系统 API。
因此:
- 只安装可信来源 DLL 插件。
- 插件作者不要读取与插件功能无关的用户数据。
- 插件不要修改启动器内部文件。
- 后台任务应支持取消。
- 网络请求应有超时。
JS 插件受 Jint runtime 限制,但如果通过 dotnet facade 加载 .NET DLL 或调用 .NET 类型,也需要同样谨慎。
capabilities 用于控制宿主主动暴露哪些 API。它不是操作系统级权限隔离。
例如未声明 ContributeTools 时不能注册工具页,但 DLL 插件本身仍运行在宿主进程内。因此能力声明用于插件生态约束和宿主 API 管理,不应被理解成完整沙箱。
- 使用 SDK 新能力时提高
minApiVersion。 - 插件发布后保持插件 ID 不变。
- 对可选 API 做 null 检查。
- 保存配置时做好默认值兼容。
- 反序列化旧配置时容忍字段缺失。
- 插件卸载应允许重复调用或半初始化状态。
WPF 控件必须在 UI 线程操作。C# 插件使用:
var ui = context.Host.Ui;
if (ui is not null)
{
ui.InvokeOnUi(() =>
{
// update WPF control
});
}JS 插件使用:
ctx.ui.run(function () {
// update UI
});长时间任务应监听取消令牌:
private CancellationTokenSource? _cts;
public override Task LoadAsync(IPluginContext context, CancellationToken cancellationToken = default)
{
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, context.HostStopping);
_ = Task.Run(() => BackgroundLoopAsync(_cts.Token), _cts.Token);
return Task.CompletedTask;
}
public override Task UnloadAsync(CancellationToken cancellationToken = default)
{
_cts?.Cancel();
_cts?.Dispose();
_cts = null;
return Task.CompletedTask;
}