插件框架


    我是個一個學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的。

后記:不知道博客存在后記不?第一次技術博客,用了很多口水話,希望大家自動忽略。確實還沒畢業很多都不太懂,里面問了不少問題,都快成博問了。希望大家諒解,並能提供指導。

源代碼:

http://files.cnblogs.com/tianfeixiang/plugin_demoV1.0001.zip


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM