-
Notifications
You must be signed in to change notification settings - Fork 1
CSharp Plugins
Ling edited this page Jul 7, 2026
·
4 revisions
C# DLL 插件适合长期维护、需要 WPF UI、需要调用 .NET 库或实现复杂逻辑的插件。
插件项目引用 NuGet.org 上的 SDK 包:
<PackageReference Include="PlainCraftLauncher.Plugin.Abstractions" Version="1.1.0" ExcludeAssets="runtime" />包页面:https://www.nuget.org/packages/PlainCraftLauncher.Plugin.Abstractions/1.1.0
注意:包 ID 是 PlainCraftLauncher.Plugin.Abstractions,但代码中使用的命名空间仍是:
using PCL.Plugin.Abstractions;推荐项目文件:
<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.1.0" 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);
}推荐继承 PclPluginBase,它提供默认卸载实现:
using System.Threading;
using System.Threading.Tasks;
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.1.0.0",
Capabilities = PluginCapabilities.ContributeTools,
LoadTiming = PluginLoadTiming.WindowCreated)]
public sealed class ExampleToolsPlugin : PclPluginBase
{
private IDisposable? _toolsPanel;
public override Task LoadAsync(IPluginContext context, CancellationToken cancellationToken = default)
{
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 System.Windows.Controls.TextBlock
{
Text = "Hello from C# plugin."
}
});
return Task.CompletedTask;
}
public override Task UnloadAsync(CancellationToken cancellationToken = default)
{
_toolsPanel?.Dispose();
_toolsPanel = null;
return Task.CompletedTask;
}
}LoadAsync:
- 读取配置。
- 注册 UI、命令、事件订阅或扩展点贡献。
- 启动轻量后台任务。
- 不要长时间阻塞 UI 线程。
UnloadAsync:
- 释放所有
IDisposable注册项。 - 取消事件订阅。
- 停止后台任务。
- 关闭文件句柄、本地进程或网络连接。
即使 LoadAsync 失败,宿主也可能调用 UnloadAsync,所以卸载逻辑应允许字段为空。
IPluginContext 提供:
| 属性 | 说明 |
|---|---|
Manifest |
当前插件 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(...)。 - 不要把插件数据写到启动器程序目录。