C# 动态加载组件类库,支持热插拔组件
新建项目IPlugin类库,里面新增IPlugin.cs(插件接口),PluginManager.cs(插件管理),FileListenerServer.cs(文件夹监控),依次如下
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace IPlugin { public interface IPlugin:IDisposable { Guid Guid { get; } string Menu { get; } string Name { get; } void Execute(); void Load(); string ShowName(); } }
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace IPlugin { public class PluginManager { /// <summary> /// 当前拥有的插件 /// </summary> public Dictionary<string, IPlugin> Plugins { get => _plugins; } private Dictionary<string, IPlugin> _plugins; /// <summary> /// 文件监听 /// </summary> private FileListenerServer _fileListener = null; private void Main(string[] args) { Console.WriteLine("可插拔插件服务"); var dic = Directory.GetCurrentDirectory(); var path = Path.Combine(dic, "plugIn"); Init(path); // 监听文件下插件变化,实现热插拔 _fileListener = new FileListenerServer(path, ref _plugins); _fileListener.Start(); Console.WriteLine("按q/Q退出"); while (true) { string input = Console.ReadLine(); switch (input) { case "q": _fileListener.Stop(); return; case "Q": _fileListener.Stop(); return; default: Console.WriteLine("按q/Q退出"); break; } } } /// <summary> /// 初始化插件 /// </summary> private void Init(string path) { Console.WriteLine(string.Format("==========【{0}】==========", "开始加载插件")); // 1.获取文件夹下所有dll文件 DirectoryInfo directoryInfo = new DirectoryInfo(path); var dlls = directoryInfo.GetFiles(); // 2.启动每个dll文件 for (int i = 0; i < dlls.Length; i++) { // 2.1 获取程序集 var fileData = File.ReadAllBytes(dlls[i].FullName); Assembly asm = Assembly.Load(fileData); var manifestModuleName = asm.ManifestModule.ScopeName; // 2.2 dll名称 var classLibrayName = manifestModuleName.Remove(manifestModuleName.LastIndexOf("."), manifestModuleName.Length - manifestModuleName.LastIndexOf(".")); Type type = asm.GetType("Plugin_Test" + "." + classLibrayName); if (!typeof(IPlugin).IsAssignableFrom(type)) { Console.WriteLine("未继承插件接口"); continue; } //dll实例化 var instance = Activator.CreateInstance(type) as IPlugin; instance.Execute(); _plugins.Add(classLibrayName, instance); //释放插件资源 instance.Dispose(); instance = null; } Console.WriteLine(string.Format("==========【{0}】==========", "插件加载完成")); Console.WriteLine(string.Format("==========【{0}】==========", "共加载插件{0}个"), _plugins.Count); } } }
using System.Collections.Generic; using System.IO; using System.Reflection; using System; namespace IPlugin { public class FileListenerServer { /// <summary> /// 文件监听 /// </summary> private FileSystemWatcher _watcher; /// <summary> /// 插件 /// </summary> private Dictionary<string, IPlugin> _iPlugin; /// <summary> /// 插件信息 /// </summary> public FileListenerServer(string path, ref Dictionary<string, IPlugin> keyValuePairs) { try { _iPlugin = keyValuePairs; this._watcher = new FileSystemWatcher(); _watcher.Path = path; _watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.DirectoryName; //_watcher.IncludeSubdirectories = true; _watcher.Created += new FileSystemEventHandler(FileWatcher_Created); _watcher.Changed += new FileSystemEventHandler(FileWatcher_Changed); _watcher.Deleted += new FileSystemEventHandler(FileWatcher_Deleted); _watcher.Renamed += new RenamedEventHandler(FileWatcher_Renamed); } catch (Exception ex) { Console.WriteLine("Error:" + ex.Message); } } public void Start() { // 开始监听 this._watcher.EnableRaisingEvents = true; Console.WriteLine(string.Format("==========【{0}】==========", "文件监控已经启动...")); } public void Stop() { this._watcher.EnableRaisingEvents = false; this._watcher.Dispose(); this._watcher = null; Console.WriteLine(string.Format("==========【{0}】==========", "文件监控已经关闭")); } /// <summary> /// 添加插件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void FileWatcher_Created(object sender, FileSystemEventArgs e) { Console.WriteLine(string.Format("==========【{0}】==========", "添加" + e.Name)); var dll = new FileInfo(e.FullPath); var fileData = File.ReadAllBytes(dll.FullName); Assembly asm = Assembly.Load(fileData); var manifestModuleName = asm.ManifestModule.ScopeName; var classLibrayName = manifestModuleName.Remove(manifestModuleName.LastIndexOf("."), manifestModuleName.Length - manifestModuleName.LastIndexOf(".")); Type type = asm.GetType("Plugin_Test" + "." + classLibrayName); // 这里默认不替换之前的插件内容 if (_iPlugin.ContainsKey(classLibrayName)) { Console.WriteLine("已经加载该插件"); return; } if (!typeof(IPlugin).IsAssignableFrom(type)) { Console.WriteLine($"{asm.ManifestModule.Name}未继承约定接口"); return; } //dll实例化 var instance = Activator.CreateInstance(type) as IPlugin; instance.Execute(); _iPlugin.Add(classLibrayName, instance); //释放插件资源 instance.Dispose(); instance = null; Console.WriteLine(string.Format("==========【{0}】==========", "共加载插件{0}个"), _iPlugin.Count); } /// <summary> /// 修改插件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void FileWatcher_Changed(object sender, FileSystemEventArgs e) { string pluginName = e.Name.Split('.')[0]; var dll = new FileInfo(e.FullPath); // 替换插件 Console.WriteLine(string.Format("==========【{0}】==========", "修改" + e.Name)); // 更新 var fileData = File.ReadAllBytes(e.FullPath); Assembly asm = Assembly.Load(fileData); var manifestModuleName = asm.ManifestModule.ScopeName; var classLibrayName = manifestModuleName.Remove(manifestModuleName.LastIndexOf("."), manifestModuleName.Length - manifestModuleName.LastIndexOf(".")); Type type = asm.GetType("Plugin_Test" + "." + classLibrayName); if (!typeof(IPlugin).IsAssignableFrom(type)) { Console.WriteLine($"{asm.ManifestModule.Name}未继承约定接口"); return; } var instance = Activator.CreateInstance(type) as IPlugin; instance.Execute(); _iPlugin[classLibrayName] = instance; instance.Dispose(); instance = null; // 避免多次触发 this._watcher.EnableRaisingEvents = false; this._watcher.EnableRaisingEvents = true; Console.WriteLine(string.Format("==========【{0}】==========", "共加载插件{0}个"), _iPlugin.Count); } /// <summary> /// 删除插件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void FileWatcher_Deleted(object sender, FileSystemEventArgs e) { Console.WriteLine(string.Format("==========【{0}】==========", "删除" + e.Name)); string pluginName = e.Name.Split('.')[0]; if (_iPlugin.ContainsKey(pluginName)) { _iPlugin.Remove(pluginName); Console.WriteLine($"插件{e.Name}被移除"); } Console.WriteLine(string.Format("==========【{0}】==========", "共加载插件{0}个"), _iPlugin.Count); } /// <summary> /// 重命名 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void FileWatcher_Renamed(object sender, RenamedEventArgs e) { //TODO:暂时不做处理 Console.WriteLine("重命名" + e.OldName + "->" + e.Name); //Console.WriteLine("重命名: OldPath:{0} NewPath:{1} OldFileName{2} NewFileName:{3}", e.OldFullPath, e.FullPath, e.OldName, e.Name); } } }
新建插件实现类库:PluginOne,继承IPlugin接口
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace PluginOne { [Serializable] public class PluginEntry:MarshalByRefObject,IPlugin.IPlugin { public Guid Guid => new Guid("21EC2020-3AEA-1069-A2DD-08002B30309D"); public string Menu => "用户系统"; public string Name => "测试权限"; public void Dispose() { throw new NotImplementedException(); } public void Execute() { throw new NotImplementedException(); } public void Load() { throw new NotImplementedException(); } public string ShowName() { return "PluginOne"; } } }
新增启动项目main,新建winform窗体,添加两个按钮;新建代理类ProxyObject.cs。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Reflection; using System.Text; using System.Windows.Forms; using IPlugin; namespace PluginMain { public partial class Form1 : Form { string assemblyName = Assembly.GetExecutingAssembly().GetName().FullName; public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { AppDomainSetup setup = new AppDomainSetup(); setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory; setup.PrivateBinPath = setup.ApplicationBase; setup.ApplicationName = "DllTest"; setup.ShadowCopyDirectories = setup.ApplicationBase + "@dlls"; setup.ShadowCopyFiles = "true"; AppDomain ad = AppDomain.CreateDomain(assemblyName, null, setup); var proxy = (ProxyObject)ad.CreateInstanceFromAndUnwrap("PluginMain.exe", typeof(ProxyObject).FullName); //var obj =Activator.CreateInstanceFrom(ad.BaseDirectory + "\\Plugin\\PluginOne.dll", "PluginOne.PluginEntry"); IPlugin.IPlugin Plugn = proxy.Create(ad.BaseDirectory + "\\Plugin\\PluginOne.dll", "PluginOne.PluginEntry", null); MessageBox.Show(Plugn.ShowName()); AppDomain.Unload(ad); } private void button2_Click(object sender, EventArgs e) { AppDomainSetup setup = new AppDomainSetup(); setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory; setup.PrivateBinPath = setup.ApplicationBase; setup.ApplicationName = "DllTest"; setup.ShadowCopyDirectories = setup.ApplicationBase + "@dlls"; setup.ShadowCopyFiles = "true"; AppDomain ad = AppDomain.CreateDomain(assemblyName, null, setup); var proxy = (ProxyObject)ad.CreateInstanceFromAndUnwrap("PluginMain.exe", typeof(ProxyObject).FullName); //var obj =Activator.CreateInstanceFrom(ad.BaseDirectory + "\\Plugin\\PluginOne.dll", "PluginOne.PluginEntry"); IPlugin.IPlugin Plugn = proxy.Create(ad.BaseDirectory + "\\Plugin\\PluginTow.dll", "PluginTow.PluginEntry", null); MessageBox.Show(Plugn.ShowName()); AppDomain.Unload(ad); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; namespace PluginMain { public class ProxyObject :MarshalByRefObject { private const BindingFlags bfi = BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance; public ProxyObject() { } public IPlugin.IPlugin Create(string assemblyFile, string typeName, object[] args) { return (IPlugin.IPlugin)Activator.CreateInstanceFrom(assemblyFile, typeName, false, bfi, null, args, null, null).Unwrap(); } } }