[熱拔插] 輕量級Winform插件式框架

寫在前面的話

對於大神,Winform這種「古董玩具」,實在沒太多「技術性」可言了,然而『好用纔是王道』,本文不以技術爲賣點,純屬經驗之談,歡迎交流拍磚小程序

樸素版UI

 

 

開發初衷

因爲本人所在公司不定時須要開發各類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字段給插件命名

 

附錄:

主源碼

插件示例

相關文章
相關標籤/搜索