zoukankan      html  css  js  c++  java
  • [热拔插] 轻量级Winform插件式框架

    写在前面的话

    对于大神,Winform这种“古董玩具”,实在没太多“技术性”可言了,然而『好用才是王道』,本文不以技术为卖点,纯属经验之谈,欢迎交流拍砖

    朴素版UI

     

    开发初衷

    由于本人所在公司不定时需要开发各种OA、数据处理小工具,需求各式各样,杂七杂八,有临时性需求开发的,有长期使用且要不定时更新的,功能一般只有一两个。又因应用不通用,所以不利于统一整合到某单一系统中,如此导致个别使用者电脑里装了玲琅满目的“小程序”。

    随着应用数目的增加,维护管理变得越来越棘手[1]。尝试从网上下载过一两个插件框架来用,使用起来虽不是很理想但也凑合[2]。后来某用户提出想要“可实时卸载、加载插件”的需求时,改造那些框架就变得很麻烦,所以干脆自己开发一个。经过几个版本的迭代,运行稳定,代码也变得简洁了。到现在也使用了好一段时间,代码也给重构了一番,所以拿出来和大家分享下。

    设计与实现

     

    框架简单明了,主体功能就在插件管理器上。插件是UserControl格式,采用.Net的反射机制进行加载。

    如此设计,出于两个目的:

    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 + ":
    " + 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应用更“顺滑”的操作预期,比如点击后的实时响应性、信息反馈、进度显示、程序不要被卡死等,所以在功能满足需求的前提下,照顾用户使用感受,也是开发人员需要多注意的(用户反馈好,说不定就有褒奖哦~)。

    谢谢阅读~

    *[1] 较早开发的程序,通用功能没有封装;通用功能封装好后,有改动,又要一个一个程序更新等

    *[2] 网上下载的框架存在冗余功能、代码,或者对某一业务针对性太强,需要进行改造

    *[3] 插件需集成自PlugInProgram.UserControlBase,类名以Uc开头——UcXXXX,使用抽象类中的ucName字段给插件命名

    附录:

    主源码

    插件示例

  • 相关阅读:
    为网站添加图标和收藏夹图标
    常用css入门
    利用反射动态创建对象
    如何用实现.NET的插件机制
    设计模式(18)-Command Pattern
    C#中调用API
    判断一个string是否可以为数字
    080709 阴
    7月9日 多云
    080710 闷热闷热
  • 原文地址:https://www.cnblogs.com/glife/p/6347203.html
Copyright © 2011-2022 走看看