插件框架

    我是个一个学GIS的。GIS软件,二维三维功能很多,很有体系。但是大部分软件体系都很大,实际应用中,GIS的功能用的可能就一项两项,然后做起来之后,维护的过程中需要添加新GIS功能。特别是对于算法依赖很高的东西。可能同一个功能,有很多种算法,各有优虐。所以,GIS平台厂商的很多平台软件都是插件式的。我现在还有几个月就要毕业了。实习做了些项目,有幸接触插件架构。写出来一个插件框架的DEMO给各位看看,合不合理。

我预计这个插件框架该有如下功能:

  1. 界面由插件组成
  2. 算法由插件组成
  3. 所有插件自己决定自己的位置和能力
  4. 所有插件可以加载和卸载

前期准备:

插件架构,无非就是通过读取DLL,得到类型,然后,通过反射得到相应实例。

我现在只知道一种方式就是:ass=Assembly.LoadFile(file); ass.GetTypes();得到相应类及实例。好像在博客园看到有人说有其他方法。不知道有没有人告诉我下。

插件要加载,卸载。有2种方式。

1.是通过把加载的类放在另一个应用程序域中,通过线程来实现主应用程序域和加载应用程序员之间通信。然后,通过实现应用程序域的加载和卸载,来达到插件的加载和卸载。

2.把插件里的类,通过创建和销毁,来实现插件的加载和卸载。

我选择了第二种,因为,简单。第一种我觉得代价太大,还有就是不敢把握。

插件怎么决定自己的能力和位置?

我现在,只做了把界面插件化,留了相应位置给算法,或者叫模型(model)。我想,界面控件,有一个父控件去盛放他;模型也有一个东西去调用他。所以我在插件基类basePlugin里定义2个属性:fatherName和Father;fatherName是写插件的时候得到的,Father是在加载插件的时候主程序去查的。也就是说,插件知道自己爸爸的名字,然后主程序就去给你把爸爸找来。

界面和model是2个不同的方式。我为了区分,用了2种方式,一个是标签,用了个叫“PluginType”的特性(Attribute)。主要用来判断改怎么去调他们的构造函数(他们都没有无参构造函数)。在basePlugin里面还定义一个属性plugin_Type,用来去那个地方找爸爸,或者还是其他什么的。反正,肯定要能通过对象,就知道对象的类型。

最后就是插件和主程序不能直接有依赖关系。

所以定义了三个程序集,一个是主程序,一个是插件程序集,一个就是纽带或者叫契约。

契约里定义,插件的基类,及各自类型的插件基类。主程序引用它,知道插件是什么样的,怎么调用。插件程序集也引用他,然后通过继承和主程序搭上关系。

下面是源代码:

先是基类

public  class basePlugin 
   { 
       string m_pluginName; 
       public basePlugin(string pluginName) 
       { 
           m_pluginName = pluginName; 
       }

       public string PluginName 
       { 
           get 
           { 
               return m_pluginName; 
           } 
       }

       public PluginType plugin_Type 
       { 
           get; 
           set; 
       }

       public virtual object Father 
       { 
           get; 
           set; 
       }

       public string fatherName 
       { 
           get; 
           set; 
       }

       /// <summary> 
       /// 加载 
       /// </summary> 
       public virtual void load() 
       { } 
       /// <summary> 
       /// 卸载 
       /// </summary> 
       public virtual void unload() 
       { } 
   } 

用于区别的插件的特性:

[global::System.AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
    public sealed class PluginTypeAttribute : Attribute
    {
        // See the attribute guidelines at 
        //  http://go.microsoft.com/fwlink/?LinkId=85236
        readonly string m_type;

        // This is a positional argument
        public PluginTypeAttribute(string type)
        {
            this.m_type = type;
        }

        public string Type
        {
            get 
            {
                return m_type;
            }
        }

        // This is a named argument
        public int NamedInt { get; set; }
    }

这里我设置的Inherited为true,AllowMutiple为假。是为了保证一个中间的契约里定义插件的分类,而不是,学具体的插件的时候,去定义;还有就是保证一个插件只有一个类别。

下面是Button类别的插件基类:

[PluginType("Control")] 
    public class ButtonPlugin:basePlugin 
    { 
        private InControl m_control; 
        private Control m_FatherControl; 
        private string m_xmlFile; 
        public ButtonPlugin(string name,string xmlfile) 
            : base(name) 
        { 

            m_control = new InControl(); 
            m_control.Run = run; 
            m_xmlFile = xmlfile; 
            setControlAttribute(m_xmlFile); 
        } 
       ……… 
        public override object Father 
        { 
            get 
            { 
                return m_FatherControl; 
            } 
            set 
            { 
                m_FatherControl = value as Control; 
            } 
        } 

        protected virtual void run() 
        { } 

        public sealed override void load() 
        { 
            if (m_FatherControl==null) 
            { 
                throw new ArgumentNullException("请先设置Father属性设置为父控件,在使用load"); 
            } 
            base.load(); 
            
            m_FatherControl.Controls.Add(m_control); 
        } 
        public sealed override void unload() 
        { 
            base.unload(); 
            m_FatherControl.Controls.Remove(m_control); 
            m_control.Dispose(); 
        } 



        private class InControl : Button 
        { 
            public Action Run 
            { 
                set; 
                get; 
            } 
            protected override void OnClick(EventArgs e) 
            { 
                base.OnClick(e); 
                Run.Invoke(); 
            } 
        } 
    

解释下,我用了嵌套类来写各类插件的基类。这样就类似于多继承,保证了我能把各自功能和控件,都能写成插件。在控件里,我没有选择事件而是选择了重载Onlick。是为了防止事件产生的内存泄漏。把内部类设置为私有,并且调用外部的虚拟方法,是为了不让子类去关心父类的是不是嵌套类。大家在留心下,load()和unload()方法。这里就是这类插件自己去描述该怎么产生,怎么去和爸爸发生关系以及怎么离开爸爸,释放自己的资源。

控件有很多属性是必须要描述。我这使用了xml来描述。

<?xml version="1.0" encoding="utf-8" ?>
<Button name="Button1" Location="{197,71}" Size="{75,23}" TabIndex="1" Text="我是插件" UseVisualStyleBackColor="true" fatherName="MainForm">
</Button>

这里请留心下fatherName,插件必须要知道爸爸的名字。ButtonPlugin代码中省略的部分就是xml解析。

在这里想大家请教下,怎么能快速得到一个控件的xml,最好能是WPF那样能够互操作的xml。这个xml是手写的。希望有知道的前辈能不吝赐教。

下面就是插件的管理类,负责管理,加载,卸载插件,并给插件找爸爸。

 sealed  class Pluginlist
    {
        private static Pluginlist m_intance;
        private Dictionary<string, basePlugin> m_plugins = new Dictionary<string, basePlugin>();
        private Dictionary<string, string> m_puginFiles = new Dictionary<string, string>();
        private Dictionary<string, Control> m_ControlsManager = new Dictionary<string, Control>();
        private string m_app_path;
        private MainForm m_app;
        private Pluginlist()
        {
            m_app_path = Directory.GetCurrentDirectory() ;
            string fileName;
            string[] dllFiles = Directory.GetFiles(m_app_path + @".\plugins", "*.dll");

            foreach (string item in dllFiles)
            {
                fileName = Path.GetFileName(item).TrimEnd(new char[] { '*','.','d','l','l'});
                m_puginFiles.Add(fileName, item);

            }

        }
。。。。
 public void LoadPlugin(string pluginName)
        {
            if (m_plugins.ContainsKey(pluginName))
            {
                return;
            }
            string file=m_puginFiles[pluginName];
            Assembly ass = Assembly.LoadFile(file);
            basePlugin plugin = GetPluginIntance(ass,pluginName);
            switch (plugin.plugin_Type)
            {
                case PluginType.Control: plugin.Father = m_ControlsManager[plugin.fatherName];
                    plugin.load();
                    break;
                case PluginType.model:
                    break;
                default:
                    break;
            }
            m_plugins.Add(plugin.PluginName, plugin);
        }

        public void unLoadPlugin(basePlugin plugin)
        {
            if (!m_plugins.ContainsValue(plugin))
            {
                return;
            }
            plugin.unload();
            m_plugins.Remove(plugin.PluginName);
        }

        public void unLoadPlugin(string pluginName)
        {
            if (!m_plugins.ContainsKey(pluginName))
            {
                return;
            }
            m_plugins[pluginName].unload();
            m_plugins.Remove(pluginName);
        }

        public basePlugin GetPluginIntance(Assembly ass,string Name)
        {
            foreach (Type type in ass.GetTypes())
            {
                if (!type.IsClass)
                {
                    continue;
                }
                if (!type.IsPublic)
                {
                    continue;
                }
                if (type.BaseType.BaseType != typeof(basePlugin))
                {
                    continue;
                }
                try
                {
                    PluginTypeAttribute[] PluginTypeAtrrs = (PluginTypeAttribute[])type.GetCustomAttributes(typeof( PluginTypeAttribute), true);
                    switch (PluginTypeAtrrs[0].Type)
                    {
                        case "Control": return TypeToIntance(type, PluginType.Control,Name);

                        case "Model": return TypeToIntance(type, PluginType.model,Name);

                        default: return null;
                    }
                }
                catch (MissingMethodException)
                {
                    return null;
                }
               
            }
            new ArgumentNullException("程序集中未找到插件!");
            return null;

        }

        private basePlugin TypeToIntance(Type plugin ,PluginType pluginType,String name)
        {
            Type[] paramType;
            ConstructorInfo constructor;
            switch (pluginType)
            {
                case PluginType.Control:paramType=new Type[2];
                    paramType[0] = typeof(String);
                    paramType[1] = typeof(String);
                    constructor = plugin.GetConstructor(paramType);
                    String param = m_app_path + @".\XMLFiles";
                    return (basePlugin)constructor.Invoke(new string[]{name ,param});

                case PluginType.model:
                    return null;
                default:
                    return null;
            }
        }
。。。。。。

 

解释下m_plugins 是所有已经加载的插件。m_puginFiles 是插件文件夹下存在的插件路径。m_ControlsManager 是所以已经存在的控件集合。m_puginFiles 为了以后能有一个插件配置界面。我在主窗体里,重载了OnControlAdded() 和OnControlRemoved()。所以m_ControlsManager 能得到所以加载到主窗体的控件。但是我还没想好,如果是一个容器控件做成插件。容器控件上的插件该怎么处理。现在,有2条思路,一条是能不能通过扩展方法,改变window的Control类;另一条是在加载容器控件的时候,通过一种方式告诉Pluginlist。

下面,是我写的一个Button插件代码很简单。

public class Button1 : ButtonPlugin
    {
        public Button1(string name, string file):base(name,file)
        {
        }
        protected override void run()
        {
            base.run();
            MessageBox.Show("YY,你大爷!");
            
        }
    }

 

YY是我同学,是他让我去写这个DEMO的。

后记:不知道博客存在后记不?第一次技术博客,用了很多口水话,希望大家自动忽略。确实还没毕业很多都不太懂,里面问了不少问题,都快成博问了。希望大家谅解,并能提供指导。

源代码:

https://files.cnblogs.com/tianfeixiang/plugin_demoV1.0001.zip
posted @ 2012-03-17 20:24  天飞翔  阅读(2240)  评论(6编辑  收藏  举报