-
Notifications
You must be signed in to change notification settings - Fork 1
CSharp Plugins
C# DLL 插件适合长期维护、需要 WPF UI、需要调用 .NET 库或实现复杂逻辑的插件。当前公开 SDK 契约位于 PCL.Plugin.Abstractions,NuGet 包 ID 为 PlainCraftLauncher.Plugin.Abstractions。
插件项目引用 SDK 包:
<PackageReference Include="PlainCraftLauncher.Plugin.Abstractions" Version="1.2.1" ExcludeAssets="runtime" />包地址:https://www.nuget.org/packages/PlainCraftLauncher.Plugin.Abstractions/
注意:包 ID 是 PlainCraftLauncher.Plugin.Abstractions,代码中使用的命名空间仍是:
using PCL.Plugin.Abstractions;如果 SDK 包尚未发布到 NuGet,可在本地开发时引用源码仓库中的 PCL.Plugin.Abstractions.csproj。
推荐项目文件:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<OutputType>Library</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PlainCraftLauncher.Plugin.Abstractions" Version="1.2.1" ExcludeAssets="runtime" />
</ItemGroup>
</Project>ExcludeAssets="runtime" 的目的,是避免插件输出目录携带 SDK 程序集。宿主会提供同一份 PCL.Plugin.Abstractions.dll,插件应复用宿主加载的契约程序集。
插件入口必须实现 IPclPlugin:
public interface IPclPlugin
{
Task LoadAsync(IPluginContext context, CancellationToken cancellationToken = default);
Task UnloadAsync(CancellationToken cancellationToken = default);
}一个插件程序集应有且仅有一个入口类型使用 [Plugin] 标注。入口类型必须是 public,并提供公共无参构造函数。
推荐继承 PclPluginBase,它会保存 Context,并创建以插件名为 category 的 Log:
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls;
using PCL.Plugin.Abstractions;
namespace Example.Tools;
[Plugin(
id: "com.example.tools",
name: "Example Tools",
version: "1.0.0.0",
Author = "Example",
Description = "Adds a tool page.",
MinApiVersion = "1.2.1.0",
Capabilities = PluginCapabilities.ContributeTools,
LoadTiming = PluginLoadTiming.WindowCreated)]
public sealed class ExampleToolsPlugin : PclPluginBase
{
private IDisposable? _toolsPanel;
public override Task LoadAsync(IPluginContext context, CancellationToken cancellationToken = default)
{
base.LoadAsync(context, cancellationToken);
var ui = context.Host.Ui ?? throw new InvalidOperationException("UI API is unavailable.");
_toolsPanel = ui.ContributeToolsPanel(new ToolsPanelDescriptor
{
Id = "example-tools",
Title = "Example Tools",
Group = "Plugins",
Icon = "lucide/wrench",
Order = 100,
Factory = () => new TextBlock { Text = "Hello from C# plugin." }
});
return Task.CompletedTask;
}
public override Task UnloadAsync(CancellationToken cancellationToken = default)
{
_toolsPanel?.Dispose();
_toolsPanel = null;
return Task.CompletedTask;
}
}C# 插件包仍需要 plugin.json:
{
"id": "com.example.tools",
"name": "Example Tools",
"version": "1.0.0.0",
"author": "Example",
"runtime": "dotnet",
"entryAssembly": "Example.Tools.dll",
"minApiVersion": "1.2.1.0",
"capabilities": ["ContributeTools"]
}plugin.json 用于安装校验、启用列表展示和定位入口 DLL。真正加载 C# 入口时,加载器会扫描 DLL 中带 [Plugin] 的 IPclPlugin 类型,并使用该 attribute 生成运行时清单。因此两处的 id、name、version、capabilities 应保持一致。
LoadAsync:
- 读取配置。
- 注册 UI、URI、事件订阅或扩展点贡献。
- 启动轻量后台任务。
- 不要长时间同步阻塞;加载器最多等待 30 秒。
UnloadAsync:
- 释放所有
IDisposable注册项。 - 取消事件订阅。
- 停止后台任务。
- 关闭文件句柄、本地进程或网络连接。
卸载逻辑应允许字段为空、允许重复调用,也要能处理 LoadAsync 只执行了一半就失败的状态。
IPluginContext 提供:
| 属性 | 说明 |
|---|---|
Manifest |
当前插件运行时清单 |
DataDirectory |
插件专属数据目录,已创建,可直接读写 |
Host |
宿主 API 门面 |
HostStopping |
宿主关闭或插件卸载取消令牌 |
示例:
var logger = context.Host.Core.GetLogger("main");
logger.Info($"Data directory: {context.DataDirectory}");
var enabled = context.Host.Config.GetBool("enabled", true);
context.Host.Config.Set("enabled", enabled);多个注册项可以集中保存:
private readonly List<IDisposable> _registrations = [];
public override Task LoadAsync(IPluginContext context, CancellationToken cancellationToken = default)
{
var events = context.Host.Events.Subscribe<MyEvent>("pcl:plugin:example", OnEventAsync);
_registrations.Add(events);
return Task.CompletedTask;
}
public override Task UnloadAsync(CancellationToken cancellationToken = default)
{
for (var i = _registrations.Count - 1; i >= 0; i--)
_registrations[i].Dispose();
_registrations.Clear();
return Task.CompletedTask;
}- 不要直接引用启动器内部项目。
- 不要把
PCL.Plugin.Abstractions.dll放进插件包。 - 不要在
LoadAsync中执行长时间同步阻塞。 - 不要在非 UI 线程直接操作 WPF 控件;使用
context.Host.Ui.InvokeOnUi(...)。 - 不要把插件运行数据写到启动器程序目录或插件安装目录;使用
context.DataDirectory。