上一篇博文实现了我的程序的自动升级,这篇来介绍我的程序的扩展部分--插件结构
园子里有很多关于插件架构的文章,有大的整个框架也有小的功能代码,每每阅读都有不少收获!
现在就来实现我的插件架构,部分内容参考学习自其它地方,在此表示感谢。
我要实现的是给Winform程序添加扩展接口,详细的就不多说了,直接按照我自己的理解来贴代码。
一,宿主程序提供公开接口供插件程序调用:
插件要如何与宿主程序通讯或使用宿主程序资源呢???
首先插件程序不可能添加对宿主程序的引用,所以首先实现一个IApplication接口,宿主程序继承自IApplication接口
IApplication接口描述宿主程序要提供给插件程序的资源(这里所说的资源应为宿主程序愿意公开的一些东西)
{
/// <summary>
/// 插件工具栏
/// </summary>
ToolStrip PluginTools{get;set;}
/// <summary>
/// 在宿主程序添加插件工具栏
/// </summary>
void CreatePluginTools();
/// <summary>
/// 插入插件工具
/// </summary>
/// <param name="plugin"></param>
void InsertPluginTool(IPlugin plugin);
}
上面的代码描述了宿主程序开放的资源,(这里的资源按照自己程序的设计需求进行提供)
第一步解决了宿主程序要如何开放接口或资源的问题,下一步就是插件程序如何使用宿主的开放资源
二,插件程序与宿主程序通讯或使用宿主程序公开的资源
在第一步已经通过 IApplication 接口文件描述了宿主程序要公开的资源和接口
插件程序要能使用这些资源和接口,则要对IApplication 接口进行实例化;
明确了这一点,那么插件接口文件就可设计就如下:
{
/// <summary>
/// 作者
/// </summary>
string Author{get;set;}
/// <summary>
/// 版本
/// </summary>
string Version{get;set;}
/// <summary>
/// 描述
/// </summary>
string Description{get;set;}
/// <summary>
/// 图标
/// </summary>
Icon Ico{get;}
/// <summary>
/// 是否自动加载
/// </summary>
bool AutoLoad { get;}
/// <summary>
/// 宿主对象
/// </summary>
IApplication application{get;set;}
/// <summary>
/// 加载
/// </summary>
void Load();
/// <summary>
/// 卸载
/// </summary>
void UnLoad();
}
IPlugin接口描述了插件程序的一些信息,描述了插件程序需要实现的2个接口 Load和UnLoad,以及最关键的 IApplication对象
有了一二两步之后,宿主程序跟插件程序如何设计都已经比较清楚了,
无非就是宿主程序实现 IApplication 接口,插件程序实现IPlugin 接口,
现在我们先来看一个实现了IPlugin 接口的插件程序
{
const bool autorun = false;
public IApplication application { get; set; }
public string Author { get;set; }
public string Version { get; set; }
public string Description { get;set; }
public Icon Ico { get { return Properties.Resource.ico; } }
public bool AutoLoad { get { return autorun; } }
public void Load()
{
Console.WriteLine("我是一个插件,我现在加载啦");
}
public void UnLoad()
{
Console.WriteLine("我现在卸载啦");
}
}
通过这个插件的实现,不难理解这个PluginDLL的application属性恰恰描述的就是一个宿主程序对象
现在假设 IApplication接口描述了一个 CreateMoney() 的方法,宿主程序实现CreateMoney()方法,但是宿主程序每隔100年才调用一次CreateMoney()方法
这时候我们肯定不愿意了,我们想一分钟就进行一次CreateMoney()方法,怎么办呢?
那我们就开发一个插件 ,插件实现一个方法
SpeedCreateMoney()
{
appliction. CreateMoney();
}
OK,这下好了,我们的插件实现了 SpeedCreateMoney()方法来快速的造钱,而这个造钱不是插件自己造,插件自己不会造,只能通过宿主程序来造, 即appliction. CreateMoney();
(上面的比喻不是很准确,只大概说明了一个宿主提供公开接口的一个例子)
到这里 问题就出现了,插件实现了SpeedCreateMoney() 方法,但是插件的appliction对象却没有实现,怎么办,当然不能自己造了,只能让宿主程序来提供
既然宿主程序提供,那宿主程序不可能依赖你插件,而只有你插件依赖宿主程序,这时候就是下一个问题咯!
三,宿主程序加载插件
这里用一个类来专门加载插件,或者也可以对插件进行管理,代码如下:
{
IApplication application;
bool bCreatePluginTools = false;
public PluginService(IApplication app)
{
application=app;
plugins = new Dictionary<string, IPlugin>();
}
/// <summary>
/// 插件字典
/// </summary>
public Dictionary<String, IPlugin> plugins;
/// <summary>
/// 加载所有插件
/// </summary>
public void LoadAllPlugins()
{
string dir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugin");
if (!Directory.Exists(dir))
return;
DirectoryInfo dirInfo = new DirectoryInfo(dir);
FileInfo[] dlls = dirInfo.GetFiles("*.dll");
foreach (FileInfo file in dlls)
{
Assembly assembly = Assembly.LoadFile(file.FullName);
try
{
Type[] types = assembly.GetTypes();
foreach (Type type in types)
{
if (type.GetInterface("IPlugin") != null)
{
IPlugin instance = (IPlugin)Activator.CreateInstance(type);
instance.application = application;
//根据XML文件读取插件属性并 set属性
ConvertXmlToPlugin(file, instance);
plugins.Add(file.Name, instance);
if (instance.AutoLoad)
{
instance.Load();
}
else
{
if (!bCreatePluginTools)
{
application.CreatePluginTools();
bCreatePluginTools = true;
}
application.InsertPluginTool(instance);
}
}
}
}
catch
{
//...插件加载异常 保存日志?
}
}
}
/// <summary>
/// 读取插件配置XML文档信息
/// </summary>
/// <param name="xmlInfo"></param>
/// <param name="plugin"></param>
private void ConvertXmlToPlugin(FileInfo xmlInfo,IPlugin plugin)
{
if (File.Exists(xmlInfo.FullName + ".xml"))
{
XmlDocument document = new XmlDocument();
document.Load(xmlInfo.FullName+".xml");
XmlElement ent = document.DocumentElement;
foreach (XmlNode node in ent.ChildNodes)
{
string xmlName=node.Name;
string xmlText = node.InnerText;
switch (xmlName)
{
case "Author":
plugin.Author = xmlText;
break;
case "Version":
plugin.Version = xmlText;
break;
case "Description":
plugin.Description = xmlText;
break;
}
}
}
else
{
plugin.Author = xmlInfo.Name;
plugin.Version = xmlInfo.Name;
plugin.Description = "缺少插件配置文件";
}
}
}
虽然这个管理类实现了一个IPluginService接口,但这并不是必须的,这里就不多说了,我们只关心宿主程序如何加载插件;
Assembly assembly = Assembly.LoadFile(file.FullName);
try
{
Type[] types = assembly.GetTypes();
foreach (Type type in types)
{
if (type.GetInterface("IPlugin") != null)
{
IPlugin instance = (IPlugin)Activator.CreateInstance(type);
instance.application = application;
//根据XML文件读取插件属性并 set属性
ConvertXmlToPlugin(file, instance);
plugins.Add(file.Name, instance);
if (instance.AutoLoad)
{
instance.Load();
}
else
{
if (!bCreatePluginTools)
{
application.CreatePluginTools();
bCreatePluginTools = true;
}
application.InsertPluginTool(instance);
}
}
}
}
catch
{
//...插件加载异常 保存日志?
}
代码很清晰,我们知道原来是通过 Assembly 加载程序集,在通过Activator.CreateInstance 来返回插件程序对象
这里只返回我们需要的,也就是实现了 IPlugin接口的对象,
OK,万事具备,有了插件程序对象,那么宿主程序就可以使用插件的方法和资源咯~~~
一般来说,插件程序只提供Load 和 UnLoad 2个方法,宿主程序加载插件后调用 Load方法,
而实际插件程序的Load方法中是宿主程序给插件程序的对象application 的一些公开方法和资源的使用!
说起来是不是很绕呢,好像陷入死循环一样的, 其实这就相当于 网络编程的Server-Client 互相可以作为接收端和发送端 (比喻不欠当请谅解)
到此为止,一个小的简单的插件结构算OK了, 在我的程序中主要做了以下工作:
1,用XML文件描述插件程序的一些信息
2,用AutoLoad字段标示插件是否需要在宿主加载时调用Load方法
3,对于不在加载时就Load方法的插件,给宿主程序添加一个工具栏名为PluginTools,并将插件程序表示在Tools工具栏里,可通过Click事件进行调用插件的Load方法
4,用一个窗体来查看宿主程序的所有插件信息
到此为止,上图一张,以及完整项目和使用示例
完整项目下载:/Files/cxwx/Plugin.rar 宿主程序的实现示例在IApplication中有包含,插件程序的实现示例看文章中PluginDLL
本着交流学习的目的,如有不对的地方希望大家批评指正!
补充说明一下:为了自己程序的需要 ,本着简化简单的原则,有些地方时按照自己需求设计的,如自动执行Load方法的插件不添加到插件工具栏等,还望大家各取所需!
没有太复杂的东西,只为自用与学习!