我的插件架构

上一篇博文实现了我的程序的自动升级,这篇来介绍我的程序的扩展部分--插件结构

园子里有很多关于插件架构的文章,有大的整个框架也有小的功能代码,每每阅读都有不少收获!

现在就来实现我的插件架构,部分内容参考学习自其它地方,在此表示感谢。

我要实现的是给Winform程序添加扩展接口,详细的就不多说了,直接按照我自己的理解来贴代码。

一,宿主程序提供公开接口供插件程序调用:

     插件要如何与宿主程序通讯或使用宿主程序资源呢???

     首先插件程序不可能添加对宿主程序的引用,所以首先实现一个IApplication接口,宿主程序继承自IApplication接口

     IApplication接口描述宿主程序要提供给插件程序的资源(这里所说的资源应为宿主程序愿意公开的一些东西)

IApplication
public interface IApplication
    {
        
/// <summary>
        
/// 插件工具栏
        
/// </summary>
        ToolStrip PluginTools{get;set;}
        
/// <summary>
        
/// 在宿主程序添加插件工具栏
        
/// </summary>
        void CreatePluginTools();
        
/// <summary>
        
/// 插入插件工具
        
/// </summary>
        
/// <param name="plugin"></param>
        void InsertPluginTool(IPlugin plugin);
    }

     上面的代码描述了宿主程序开放的资源,(这里的资源按照自己程序的设计需求进行提供)

     第一步解决了宿主程序要如何开放接口或资源的问题,下一步就是插件程序如何使用宿主的开放资源

二,插件程序与宿主程序通讯或使用宿主程序公开的资源

     在第一步已经通过 IApplication 接口文件描述了宿主程序要公开的资源和接口

     插件程序要能使用这些资源和接口,则要对IApplication 接口进行实例化;

     明确了这一点,那么插件接口文件就可设计就如下:

IPlugin
public interface IPlugin
    {
        
/// <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 接口的插件程序

PluginDLL
public class PluginK : IPlugin
    {
        
const bool autorun = false;
        
public IApplication application { getset; }
        
public string Author { get;set; }
        
public string Version { getset; }
        
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对象却没有实现,怎么办,当然不能自己造了,只能让宿主程序来提供

既然宿主程序提供,那宿主程序不可能依赖你插件,而只有你插件依赖宿主程序,这时候就是下一个问题咯!

三,宿主程序加载插件

     这里用一个类来专门加载插件,或者也可以对插件进行管理,代码如下:

PluginService
 public class PluginService:IPluginService
    {
        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方法的插件不添加到插件工具栏等,还望大家各取所需! 

   没有太复杂的东西,只为自用与学习! 

原文地址:https://www.cnblogs.com/cxwx/p/1768395.html