Skip to content

CSharp Plugins

Ling edited this page Jul 7, 2026 · 4 revisions

C# DLL 插件

C# DLL 插件适合长期维护、需要 WPF UI、需要调用 .NET 库或实现复杂逻辑的插件。

SDK 包

插件项目引用 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(...)
  • 不要把插件数据写到启动器程序目录。

Clone this wiki locally