动态插件加载框架

在当今的软件开发中,灵活性和动态性是应用开发的重要需求。然而,许多应用程序在更改后需要重启,这无疑是对时间的极大浪费。为了解决这一问题,进行了一项简短的市场调查,以了解在.Net环境下实现插件架构的三种方式:MEF、MAF和反射。但是,这些方法都没有完全满足需求。

MEF(Managed Extensibility Framework)虽然提供了插件加载的功能,但它阻止了在运行时更改DLL文件,并且所有DLL都在同一个AppDomain中加载,如果其中一个插件抛出未处理的异常,整个应用程序可能会崩溃。MAF(Managed Addin Framework)要求插件文件夹具有预定义的结构,并且在插件实现方面实现复杂。反射虽然是一个低级别的实现方式,但不想重新发明轮子。

框架特性

该框架基于FileSystemWatcher、AppDomain和MEF技术,主要特点包括:

  • 支持任何结构的插件文件夹,包括子文件夹、同一文件夹中的多个插件DLL、单个DLL中的多个插件实现。
  • 在运行时创建、重命名、替换、删除插件文件夹或文件。
  • 在加载/重新加载插件时不会造成内存泄漏。
  • 插件实现中需要的额外代码最少。
  • 支持带有参数化构造函数的插件加载。
  • 插件可以具有任何常规语法(泛型方法、动态、事件等)。
  • 插件可以有任意附加依赖。
  • 插件中的未处理异常不会使宿主应用程序崩溃。

使用框架

框架基本上监控指定的文件夹及其所有子文件夹上的创建/更改/重命名/删除事件。当发生“创建”事件时,框架会创建一个单独的AppDomain,并将插件加载到该域中。为了避免阻塞文件,AppDomain会创建一个创建文件夹的阴影副本。当文件夹或其根文件发生变化/重命名/删除时,框架会删除旧的AppDomain并创建一个新的,其中包含插件的新实例。因此,每个文件夹将有一个AppDomain。唯一的限制是,从插件传递到宿主应用程序的对象和反之亦然的对象应该是MarshalByRefObject。这是因为对象需要通过AppDomain边界传递。

首先需要定义一个合约,这是宿主应用程序和插件之间共享的接口。它应该在共享程序集中定义:

public interface IPlugin : IDisposable { string Name { get; } string SayHelloTo(string personName); }

倾向于每个项目实现一个插件,但并没有限制实现多个插件。

using Contracts; using System; using System.ComponentModel.Composition; namespace Plugin1 { [Export(typeof(IPlugin))] public class Plugin : BasePlugin { public Plugin() : base("Plugin1") { Console.WriteLine("ctor_{0}", Name); } public override string SayHelloTo(string personName) { string hello = string.Format("Hello {0} from {1}.", personName, Name); return hello; } public override void Dispose() { Console.WriteLine("dispose_{0}", Name); } } }

以控制台应用程序为例,需要添加以下引用:

  • Contracts(包含合约的程序集)
  • Mark42(插件框架本身)
using Contracts; using Mark42; using System; using System.Collections.Generic; using System.IO; namespace TestConsole { class Program { static void Main(string[] args) { var pluginsFolderPath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Plugins"); PluginService<IPlugin> pluginService = new PluginService<IPlugin>(pluginsFolderPath, "*.dll", true); pluginService.PluginsAdded += pluginService_PluginAdded; pluginService.PluginsChanged += pluginService_PluginChanged; pluginService.PluginsRemoved += pluginService_PluginRemoved; pluginService.Start(); Console.ReadKey(); pluginService.Stop(); } #region Event handlers private static void pluginService_PluginRemoved(PluginService<IPlugin> sender, List<IPlugin> plugins) { foreach (var plugin in plugins) { Console.WriteLine("PluginRemoved: {0}.", plugin.Name); plugin.Dispose(); } } private static void pluginService_PluginChanged(PluginService<IPlugin> sender, List<IPlugin> oldPlugins, List<IPlugin> newPlugins) { Console.WriteLine("PluginChanged: {0} plugins -> {1} plugins.", oldPlugins.Count, newPlugins.Count); foreach (var plugin in oldPlugins) { Console.WriteLine("~removed: {0}.", plugin.Name); plugin.Dispose(); } foreach (var plugin in newPlugins) { Console.WriteLine("~added: {0}.", plugin.Name); } } private static void pluginService_PluginAdded(PluginService<IPlugin> sender, List<IPlugin> plugins) { foreach (var plugin in plugins) { Console.WriteLine("PluginAdded: {0}.", plugin.Name); Console.WriteLine(plugin.SayHelloTo("Tony Stark")); } } #endregion } } [Export(typeof(IPlugin))] public class Plugin : BasePlugin { [ImportingConstructor] public Plugin(CustomParameters parameters) : base("Plugin1") { Console.WriteLine("ctor_{0}", Name); } public override string SayHelloTo(string personName) { string hello = string.Format("Hello {0} from {1}.", personName, Name); return hello; } public override void Dispose() { Console.WriteLine("dispose_{0}", Name); } }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485