[热拔插] 轻量级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 + ":\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应用更“顺滑”的操作预期,比如点击后的实时响应性、信息反馈、进度显示、程序不要被卡死等,所以在功能满足需求的前提下,照顾用户使用感受,也是开发人员需要多注意的(用户反馈好,说不定就有褒奖哦~)。
谢谢阅读~
*[1] 较早开发的程序,通用功能没有封装;通用功能封装好后,有改动,又要一个一个程序更新等
*[2] 网上下载的框架存在冗余功能、代码,或者对某一业务针对性太强,需要进行改造
*[3] 插件需集成自PlugInProgram.UserControlBase,类名以Uc开头——UcXXXX,使用抽象类中的ucName字段给插件命名
附录:
【主源码】
『插件示例』
[热拔插] 轻量级Winform插件式框架的更多相关文章
- 【插件式框架探索系列】应用程序域(AppDomain)
应用程序域(AppDomain)已经不是一个新名词了,只要熟悉.net的都知道它的存在,不过我们还是先一起来重新认识下应用程序域吧,究竟它是何方神圣. 应用程序域 众所周知,进程是代码执行和资源分配的 ...
- QT/C++插件式框架、利用智能指针管理内存空间的实现、动态加载动态库文件
QT.C++插件式框架.主要原理还是 动态库的动态加载. dlopen()函数.下面为动态加载拿到Plugininstance对应指针.void**pp=(void**)dlsym(handle,&q ...
- Python3漏洞扫描工具 ( Python3 插件式框架 )
目录 Python3 漏洞检测工具 -- lance screenshot requirements 关键代码 usage documents README Guide Change Log TODO ...
- 基于Python3的漏洞检测工具 ( Python3 插件式框架 )
目录 Python3 漏洞检测工具 -- lance screenshot requirements 关键代码 usage documents Any advice or sugggestions P ...
- 【插件式框架探索系列】使用多UI线程提升性能
了解WPF线程模型的都知道,UI线程负责呈现和管理UI,而UI元素(派生自 DispatcherObject)只能由创建该元素的线程来访问,这就导致了一些耗时的UI操作将影 响到整个应用程序性能,未响 ...
- asp.mvc 插件式框架
参考文档: http://blog.csdn.net/bitfan/article/details/17260775 http://www.cnblogs.com/Mainz/archive/2012 ...
- (1)从底层设计,探讨插件式GIS框架的实现
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 研一时,听当时的师兄推荐,买了蒋波涛的一本关于GIS插件框架的书.当时 ...
- 基于OSGI.NET的MVC插件式开发
最近在研究OSGI.NET插件式开发框架.官方网站提供了一个基于OSGI.NET的插件仓库.下载官方的SDK包安装后VS项目模板会多出一组iOpenWorks项目模板.在学习过程中,发现通过iOpen ...
- 使用 SailingEase WinForm 框架构建复合式应用程序(插件式应用程序)
对于一些较小的项目,具备一定经验的开发人员应该能够设计和构建出便于进行维护和扩展的应用程序.但是,随着功能模块数量(以及开发维护这些部件的人员)的不断增加,对项目实施控制的难度开始呈指数级增长. Sa ...
随机推荐
- 基本SQL 语句操作数据增删查改
1.创建数据库: create database <数据库名>. 如:create database student; 2.连接到一个已经存在的数据库: use <数据库名>: ...
- 【转载】一张“神图”看懂单机/集群/热备/磁盘阵列(RAID)
单机部署(stand-alone):只有一个饮水机提供服务,服务只部署一份 集群部署(cluster):有多个饮水机同时提供服务,服务冗余部署,每个冗余的服务都对外提供服务,一个服务挂掉时依然可用 热 ...
- Task C# 多线程和异步模型 TPL模型 【C#】43. TPL基础——Task初步 22 C# 第十八章 TPL 并行编程 TPL 和传统 .NET 异步编程一 Task.Delay() 和 Thread.Sleep() 区别
Task C# 多线程和异步模型 TPL模型 Task,异步,多线程简单总结 1,如何把一个异步封装为Task异步 Task.Factory.FromAsync 对老的一些异步模型封装为Task ...
- 编程算法 - 数组中出现次数超过一半的数字 代码(C)
数组中出现次数超过一半的数字 代码(C) 本文地址: http://blog.csdn.net/caroline_wendy 题目: 数组中有一个数字出现的次数超过数组长度的一半, 请找出这个数字. ...
- Hadoop如何计算map数和reduce数
阅读本文可以带着下面问题: 1.map和reduce的数量过多会导致什么情况? 2.Reduce可以通过什么设置来增加任务个数? 3.一个task的map数量由谁来决定? 4.一个task的reduc ...
- python dictionary的遍历
d = {'x':1, 'y':3, 'z':2} for k in d: print d[k] 直接遍历k in d的话,遍历的是dictionary的keys. 2 字典的键可以是任何不可变 ...
- 对云资源服务商资源读写的架构思考:前端代码走token
第一.统一了访问端接口.提高前端开发速度:第二统一了阿里各个产品的 数据读写模式: 第三,我们的服务器产生token时对读写规则做限制,特定的token由特定的规则产生,而不是让前端代代码去管控限制 ...
- Introduce Null Object
今天继续总结<重构>这本书中的一个重构手法,Introduce Null Object.写这个手法是因为它确实很巧妙,在实际编程中经常会遇到这种情况,前人总结出来了这么一个经典的手法,当然 ...
- manacher求最长回文子串算法模板
#include <iostream> #include <cstring> #include <cstdlib> #include <stdio.h> ...
- 蓝牙协议(bluetooth spec)
1.概述: 蓝牙协议规范遵循开放系统互连参考模型(OSI/RM),从低到高地定义了蓝牙协议堆栈的各个层次. SIG(Session Initiation Protocol)所定义的蓝牙技术规范的目 ...