新建專案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(); } } }