# 05-插件间通信 插件之间可以通过消息总线和服务导出进行通信,实现功能协作和数据共享。 --- ## 🎯 通信方式概述 | 方式 | 适用场景 | 方向 | |-----|---------|------| | **消息总线** | 事件通知、广播 | 多对多 | | **服务导出** | 功能共享、API 暴露 | 一对多 | | **共享契约** | 数据交换 | 双向 | --- ## 📢 消息总线 使用 `IPluginMessageBus` 发布和订阅消息。 ### 发布消息 ```csharp public class WeatherService { private readonly IPluginMessageBus _messageBus; public WeatherService(IPluginMessageBus messageBus) { _messageBus = messageBus; } public async Task UpdateWeatherAsync() { var weather = await FetchWeatherAsync(); // 发布天气更新消息 _messageBus.Publish(new WeatherUpdatedMessage { City = weather.City, Temperature = weather.Temperature, Condition = weather.Condition }); } } // 定义消息 public class WeatherUpdatedMessage { public string City { get; set; } = ""; public double Temperature { get; set; } public string Condition { get; set; } = ""; } ``` ### 订阅消息 ```csharp public class AnotherPluginService { public AnotherPluginService(IPluginMessageBus messageBus) { // 订阅天气更新消息 messageBus.Subscribe(OnWeatherUpdated); } private void OnWeatherUpdated(WeatherUpdatedMessage message) { Console.WriteLine($"收到天气更新: {message.City} {message.Temperature}°C"); // 根据天气更新自己的状态 UpdateDisplay(message); } } ``` ### 取消订阅 ```csharp public class MyService : IDisposable { private readonly IPluginMessageBus _messageBus; private readonly Guid _subscriptionId; public MyService(IPluginMessageBus messageBus) { _messageBus = messageBus; _subscriptionId = messageBus.Subscribe(OnWeatherUpdated); } public void Dispose() { // 取消订阅,避免内存泄漏 _messageBus.Unsubscribe(_subscriptionId); } } ``` --- ## 🔌 服务导出 插件可以将服务导出,供其他插件使用。 ### 导出服务 ```csharp public override void Initialize(HostBuilderContext context, IServiceCollection services) { // 注册服务 services.AddSingleton(); // 导出服务供其他插件使用 services.AddPluginServiceExport( serviceKey: "MyPlugin.WeatherService", description: "提供天气查询服务"); } // 定义服务接口 public interface IWeatherService { Task GetCurrentWeatherAsync(string city); Task> GetForecastAsync(string city, int days); } // 实现服务 public class WeatherService : IWeatherService { public async Task GetCurrentWeatherAsync(string city) { // 实现天气查询 } public async Task> GetForecastAsync(string city, int days) { // 实现天气预报 } } ``` ### 使用其他插件的服务 ```csharp public class MyWidget : Border { public MyWidget(PluginDesktopComponentContext context) { // 获取其他插件导出的服务 var weatherService = context.ServiceProvider .GetExportedService("MyPlugin.WeatherService"); if (weatherService != null) { // 使用服务 LoadWeatherAsync(weatherService); } } private async void LoadWeatherAsync(IWeatherService weatherService) { var weather = await weatherService.GetCurrentWeatherAsync("北京"); UpdateUI(weather); } } ``` ### 服务导出选项 ```csharp services.AddPluginServiceExport( serviceKey: "MyPlugin.WeatherService", description: "提供天气查询服务", version: "1.0.0", isPublic: true); // 是否公开给其他插件 ``` --- ## 📦 共享契约 通过 `sharedContracts` 在插件间共享类型定义。 ### 定义共享契约 ```csharp // 在共享类库项目中定义 namespace MyPlugin.Shared; public interface IWeatherData { string City { get; } double Temperature { get; } string Condition { get; } } public class WeatherData : IWeatherData { public string City { get; set; } = ""; public double Temperature { get; set; } public string Condition { get; set; } = ""; } ``` ### 在 plugin.json 中声明 ```json { "id": "com.example.weather", "name": "天气插件", "sharedContracts": [ "MyPlugin.Shared.IWeatherData", "MyPlugin.Shared.WeatherData" ] } ``` ### 使用共享类型 ```csharp // 插件 A 发布数据 public class WeatherService { public WeatherData GetWeather() { return new WeatherData { City = "北京", Temperature = 25.5, Condition = "晴" }; } } // 插件 B 接收数据 public class ConsumerService { public void ProcessWeather(IWeatherData weather) { Console.WriteLine($"{weather.City}: {weather.Temperature}°C"); } } ``` --- ## 🔒 安全考虑 ### 服务导出安全 ```csharp // 只导出必要的接口,不暴露实现细节 public interface IPublicApi { Task GetDataAsync(); } internal class InternalService : IPublicApi { // 内部实现细节不暴露 private readonly SecretKey _key; public async Task GetDataAsync() { // 实现 } } ``` ### 消息验证 ```csharp private void OnMessageReceived(MyMessage message) { // 验证消息来源 if (message.SenderId != "TrustedPlugin") { return; // 忽略不信任来源的消息 } // 处理消息 } ``` --- ## 💡 最佳实践 ### 1. 使用接口定义服务契约 ```csharp // ✅ 好的做法 - 定义接口 public interface IWeatherService { } // ❌ 避免 - 直接导出实现类 services.AddPluginServiceExport(...); ``` ### 2. 处理服务不可用情况 ```csharp // ✅ 优雅处理服务缺失 var service = context.ServiceProvider .GetExportedService("key"); if (service == null) { // 显示提示或降级处理 ShowServiceUnavailableMessage(); return; } ``` ### 3. 及时取消消息订阅 ```csharp // ✅ 在 Dispose 中取消订阅 public void Dispose() { _messageBus.Unsubscribe(_subscriptionId); } ``` ### 4. 版本兼容性 ```csharp // 在服务导出中包含版本信息 services.AddPluginServiceExport( serviceKey: "MyPlugin.WeatherService", version: "2.0.0", // 语义化版本 description: "天气服务 v2"); ``` --- ## 🐛 常见问题 ### 问题 1:消息收不到 **排查:** 1. 确认消息类型完全一致(包括命名空间) 2. 检查订阅是否在消息发布之前 3. 确认没有取消订阅 ### 问题 2:服务找不到 **排查:** 1. 确认服务已导出(`AddPluginServiceExport`) 2. 检查 `serviceKey` 是否正确 3. 确认依赖的插件已安装并启用 ### 问题 3:类型转换错误 **原因:** 共享契约类型不匹配 **解决:** 确保所有插件使用相同版本的共享契约程序集 --- ## 📚 参考资源 - [IPluginMessageBus 源码](../../LanMountainDesktop.PluginSdk/IPluginMessageBus.cs) - [IPluginExportRegistry 源码](../../LanMountainDesktop.PluginSdk/IPluginExportRegistry.cs) - [Shared.Contracts](../../LanMountainDesktop.Shared.Contracts/) --- ## 🎯 下一步 查看实战案例: 👉 **[01-开发天气组件](../04-实战案例/01-开发天气组件.md)** - 完整插件开发流程 --- *最后更新:2026年4月*