對於大神,Winform這種「古董玩具」,實在沒太多「技術性」可言了,然而『好用纔是王道』,本文不以技術爲賣點,純屬經驗之談,歡迎交流拍磚小程序
因爲本人所在公司不定時須要開發各類OA、數據處理小工具,需求各式各樣,雜七雜八,有臨時性需求開發的,有長期使用且要不定時更新的,功能通常只有一兩個。又因應用不通用,因此不利於統一整合到某單一系統中,如此致使個別使用者電腦裏裝了玲琅滿目的「小程序」。框架
隨着應用數目的增長,維護管理變得愈來愈棘手[1]。嘗試從網上下載過一兩個插件框架來用,使用起來雖不是很理想但也湊合[2]。後來某用戶提出想要「可實時卸載、加載插件」的需求時,改造那些框架就變得很麻煩,因此乾脆本身開發一個。通過幾個版本的迭代,運行穩定,代碼也變得簡潔了。到如今也使用了好一段時間,代碼也給重構了一番,因此拿出來和你們分享下。工具
框架簡單明瞭,主體功能就在插件管理器上。插件是UserControl格式,採用.Net的反射機制進行加載。測試
如此設計,出於兩個目的:this
1)插件功能高內聚,與框架低耦合。開發人員根據規範[3]開發並測試好後,直接接入框架便可。也可單獨編譯成單一程序插件
2)方便將原來的應用經過簡單改造變成插件加載到框架中線程
/// <summary> /// 加載PlugIns插件目錄下的dll /// </summary> public static List<UserControlBase> GetPlugIns() { List<UserControlBase> lUc = new List<UserControlBase>(); foreach (var dllFile in Directory.GetFiles(PlugInsDir)) { FileInfo fi = new FileInfo(dllFile); if (!fi.Name.EndsWith(".dll")) continue; foreach (var _uc in CreatePluginInstance(fi.FullName)) { if (_uc != null) { lUc.Add(_uc); } } } return lUc; } /// <summary> /// 根據全名和路徑構造對象 /// </summary> /// <param name="sFilePath">程序集路徑</param> /// <returns></returns> public static List<UserControlBase> CreatePluginInstance(string sFilePath, Type hostType = null) { List<UserControlBase> lUc = new List<UserControlBase>(); try { lUc = CreateInstance(sFilePath, new string[] { "Uc" }, hostType); } catch (Exception ex) { Console.WriteLine("CreateInstance: " + ex.Message); } return lUc; } /// <summary> /// 反射建立實例 /// </summary> /// <param name="sFilePath"></param> /// <param name="typeFeature"></param> /// <param name="hostType"></param> /// <param name="dynamicLoad"></param> /// <returns></returns> public static List<UserControlBase> CreateInstance(string sFilePath, string[] typeFeature, Type hostType = null, bool dynamicLoad = true) { var lUc = new List<UserControlBase>(); Assembly assemblyObj = null; if (!dynamicLoad) { #region 方法一:直接從DLL路徑加載 assemblyObj = Assembly.LoadFrom(sFilePath); #endregion } else { #region 方法二:先把DLL加載到內存,再從內存中加載(可在程序運行時動態更新dll文件,比藉助AppDomain方便多了!) using (FileStream fs = new FileStream(sFilePath, FileMode.Open, FileAccess.Read)) { using (BinaryReader br = new BinaryReader(fs)) { byte[] bFile = br.ReadBytes((int)fs.Length); br.Close(); fs.Close(); assemblyObj = Assembly.Load(bFile); } } #endregion } if (assemblyObj != null) { #region 讀取dll內的全部類,生成實例(這樣可省去提供 命名空間 的步驟) // 程序集(命名空間)中的各類類 foreach (Type type in assemblyObj.GetTypes()) { try { if (type.ToString().Contains("<>")) continue; if (typeFeature != null) { bool invalidInstance = true; foreach (var tf in typeFeature) { if (type.ToString().Contains(tf)) { invalidInstance = false; break; } } if (invalidInstance) continue; } var uc = (UserControlBase)assemblyObj.CreateInstance(type.ToString()); //反射建立 lUc.Add(uc); if (hostType != null) { AssemblyInfoHelper aih = new AssemblyInfoHelper(hostType); } } catch (InvalidCastException icex) { Console.WriteLine(icex); } catch (Exception ex) { throw new Exception("Create " + sFilePath + "(" + type.ToString() + ") occur " + ex.GetType().Name + ":\r\n" + ex.Message + (ex.InnerException != null ? "(" + ex.InnerException.Message + ")" : "")); } } #endregion } return lUc; } /// <summary> /// 加載插件 /// </summary> void LoadPlugIns() { // 整理UI tvPlugins.Nodes.Clear(); lPlugIn.Clear(); dicLoadedUCs.Clear(); #region 逐一加載UC string[] DllFiles = Directory.GetFiles(LoadPlugInManager.PlugInsDir); string dllFile = ""; for (int f = 0; f < DllFiles.Length; f++) { dllFile = DllFiles[f]; FileInfo fi = new FileInfo(dllFile); if (!fi.Name.EndsWith(".dll")) continue; ThreadHelper.RunInAdditionalThread(new DlgtVoidMethod(() => { // 該部分在另外一線程中完成,因此不會卡住當前窗體 foreach (var uc in CreatePluginInstance(fi.FullName, this.GetType())) { if (uc != null) { // 保存到已加載UC字典 if (!dicLoadedUCs.ContainsKey(uc.UCName)) { dicLoadedUCs.Add(uc.UCName, new List<UserControlBase>()); dicLoadedUCs[uc.UCName].Add(uc); lPlugIn.Add(uc); // 這裏通知窗體線程,加載到插件樹控件中(供用戶點擊選擇相應控件) ThreadHelper.RunInAdditionalThread(new DlgtVoidMethod_withParam((Object obj) => { UserControlBase _uc = obj as UserControlBase; TreeNode _tn_ = null; foreach (TreeNode n in tvPlugins.Nodes) { if (n.Text == _uc.UCTpye) { _tn_ = n; break; } } if (_tn_ == null) { _tn_ = new TreeNode(_uc.UCTpye); tvPlugins.Nodes.Add(_tn_); } TreeNode _n_ = new TreeNode(_uc.UCName); _n_.ToolTipText = _uc.Recommend; _tn_.Nodes.Add(_n_); tvPlugins.ExpandAll(); Log("App", "成功加載:" + _uc.UCName); }) , uc , new DlgtVoidMethod_withParam(delegate (Object oEx) { MessageBox.Show((oEx as Exception).Message); }) , tvPlugins); } } } }) , new DlgtVoidMethod_withParam(delegate (Object oEx) { MessageBox.Show((oEx as Exception).Message); })); } #endregion }
這裏,最重要的插件「熱拔插」功能,就是使用CreateInstance中方法二來將dll加載到內存,而後再進行實例化,如此,dll文件在程序加載插件完畢後,就可完美「脫身」,又可在程序運行時,從新加載(指定dll)。設計
用戶在使用本地應用時,每每想要有比Web應用更「順滑」的操做預期,好比點擊後的實時響應性、信息反饋、進度顯示、程序不要被卡死等,因此在功能知足需求的前提下,照顧用戶使用感覺,也是開發人員須要多注意的(用戶反饋好,說不定就有褒獎哦~)。3d
*[1] 較早開發的程序,通用功能沒有封裝;通用功能封裝好後,有改動,又要一個一個程序更新等orm
*[2] 網上下載的框架存在冗餘功能、代碼,或者對某一業務針對性太強,須要進行改造
*[3] 插件需集成自PlugInProgram.UserControlBase,類名以Uc開頭——UcXXXX,使用抽象類中的ucName字段給插件命名
附錄:
【主源碼】
『插件示例』