[热拔插] 轻量级Winform插件式框架

news/2024/10/31 5:24:36/

写在前面的话

对于大神,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 + ":\r\n" + ex.Message + (ex.InnerException != null ? "(" + ex.InnerException.Message + ")" : ""));}}#endregion}return lUc;
}/// <summary>
/// 加载插件
/// </summary>
void LoadPlugIns()
{// 整理UItvPlugins.Nodes.Clear();lPlugIn.Clear();dicLoadedUCs.Clear();#region 逐一加载UCstring[] 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字段给插件命名

 

附录:

【主源码】

『插件示例』

 

转载于:https://www.cnblogs.com/glife/p/6347203.html


http://www.ppmy.cn/news/483431.html

相关文章

[嵌入式linux]PCIe 热拔插(rescan)

linux下可通过/sys/bus/pci/devices/0000\:[bus number]\:[device number].[function number]/ 目录下的节点进行热拔插操作。 板子上电前PCIe插槽有一块NVME的固态硬盘 [ 0.198515] pci 0000:00:00.0: [16c3:abcd] type 01 class 0x060400 [ 0.199284] pci 0000:01:00…

几个SQL的高级写法

一、ORDER BY FLELD() 自定义排序逻辑 MySql 中的排序 ORDER BY 除了可以用 ASC 和 DESC&#xff0c;还可以通过 ORDER BY FIELD(str,str1,...) 自定义字符串/数字来实现排序。这里用 order_diy 表举例&#xff0c;结构以及表数据展示&#xff1a; ORDER BY FIELD(str,str1,..…

关于微信小程序生成海报一个简单的办法

废话不多说&#xff0c;直接入题&#xff0c;先上GITHUB地址&#xff0c;这个组件很好用&#xff0c;有图形生成工具&#xff0c;你不用再自己写代码一个个元素对齐了&#xff0c;是不是很爽。 GITHUB&#xff1a;https://github.com/Kujiale-Mobile/Painter 生成painter代码…

TC8:SOMEIPSRV_OPTIONS_12-15

SOMEIPSRV_OPTIONS_12: Reserved field of the IPv4 Multicast Option 目的 IPv4 Multicast Option的Reserved字段应静态设置为0x00 这里指的是第二个Reserved字段 测试步骤 DUT CONFIGURE:启动具有下列信息的服务Service ID:SERVICE-ID-1Instance数量:1Tester:客户端-1发…

Jconsole 开启远程连接遇到的一些坑

最近在学习 JVM&#xff0c;其中涉及到性能、内存等指标分析需要使用工具分享&#xff0c;Java 提供了几个可视化工具来监控和管理 Java 应用&#xff0c;比如 Jconsole、JVisual、JMC&#xff0c;他们以图形化的界面实时的监控程序各种性能指标以及内存、CPU 的使用情况。 Jco…

Prometheus配置通过file_sd_configs中每个目标的module标签信息重置每个目标的metrics_path

配置方式如下: scrape_configs: - job_name: file_sd file_sd_configs:- files: - targets.jsonrelabel_configs:- source_labels: [__address__]regex: (http://)([^:])target_label: __address__ replacement: http://${2}- source_labels: [__port__] regex: (\d)target_la…

Davinci安装失败

Davinci安装失败 在安装Davinci的时候&#xff0c;遇到报错Failed to install DaVinci Resolve Panels. Continues with others components? 更新最新显卡驱动没有解决问题&#xff0c;后来想到可能是我把TMP&#xff0c;TEMP文件夹放到ramdisk&#xff0c;导致空间不够。在把…

基于FPGA的AD/DA实验

掌握并行DAC、ADC的接口时序 DDS信号的产生 参考《基于FPGA的DDS实现》用DDS合成信号&#xff0c;经过DAC输出用ADC采集信号 高速AD/DA转化器 AD9762是无符号的DAC器件 有符号补码需要先把高位取反再送给DAC* AD9200是无符号的ADC 最大电压对应MAX值 0电压对应0值 注意ADC芯…