C#反射實現外掛式開發

2023-09-16 06:00:53

前言

外掛式架構,一種全新的、開放性的、高擴充套件性的架構體系。外掛式架構設計好處很多,把擴充套件功能從框架中剝離出來,降低了框架的複雜度,讓框架更容易實現。擴充套件功能與框架以一種很鬆的方式耦合,兩者在保持介面不變的情況下,可以獨立變化和釋出。基於外掛設計並不神祕,相反它比起一團泥的設計更簡單,更容易理解。

專案介紹

書寫4個外掛類庫,分別傳參實現「加減乘除」運算,呼叫外掛的使用者端採用Winform表單程式。

目標框架:.NET Framework 4.6.1

專案架構和表單佈局:

使用者端程式:

  • PluginApp:反射呼叫外掛

外掛描述:

  • PluginBase:規範外掛的基礎類別,定義抽象類,開發的外掛的類需要繼承此類,代表遵守這個規範。
  • CustomPlugInA:實現加法的外掛
  • CustomPlugInB:實現減法的外掛
  • CustomPlugInC:實現乘法的外掛
  • CustomPlugInD:實現除法的外掛

程式碼實現

外掛基礎類別
 /// <summary>
    ///外掛基礎類別
    /// </summary>
    public abstract class Base
    {
        /// <summary>
        /// 外掛名稱
        /// </summary>
        /// <returns></returns>
        public abstract string Name();
       /// <summary>
       /// 外掛描述
       /// </summary>
       /// <returns></returns>
        public abstract string Desc();
        /// <summary>
        /// 執行方法
        /// </summary>
        /// <param name="param1">引數1</param>
        /// <param name="param2">引數2</param>
        /// <returns></returns>
        public abstract string Run(int param1, int param2);
        /// <summary>
        /// 版本 
        /// </summary>
        public string Version
        {
            get { return "1.0.0"; }
        }
    }
PlugInA
    public class PlugInA: Base
    {

        public override string Name()
        {
            return "PlugInA";
        }

        public override string Desc()
        {
            return "加法";
        }

        public override string Run(int param1,int param2)
        {
            return (param1 + param2) + "";
        }
    }
}
PlugInB
    public class PlugInB : Base
    {

        public override string Name()
        {
            return "PlugInB";
        }

        public override string Desc()
        {
            return "減法";
        }

        public override string Run(int param1, int param2)
        {
            return (param1 - param2) + "";
        }
    }
PlugInC
  public class PlugInC : Base
    {

        public override string Name()
        {
            return "PlugInC";
        }

        public override string Desc()
        {
            return "乘法";
        }

        public override string Run(int param1, int param2)
        {
            return (param1 * param2) + "";
        }
    }
PlugInD
 public class PlugInD : Base
    {

        public override string Name()
        {
            return "PlugInD";
        }

        public override string Desc()
        {
            return "除法";
        }

        public override string Run(int param1, int param2)
        {
            return (param1 / param2) + "";
        }
    }
使用者端核心程式碼:
   public partial class FrmMain : Form
    {
        public FrmMain()
        {
            InitializeComponent();
            dgrvPlugins.AutoGenerateColumns = false;
        }

        List<PluginModel> List = new List<PluginModel>();

        readonly string PlugInPath = Application.StartupPath + "\\PlugIns";

        /// <summary>
        /// 載入外掛
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btLoadPlugins_Click(object sender, EventArgs e)
        {

            if (!Directory.Exists(PlugInPath))
            {
                Directory.CreateDirectory(PlugInPath);
            }
            List.Clear();
            string[] files = Directory.GetFiles(PlugInPath);
            foreach (string file in files)
            {
                if (file.ToLower().EndsWith(".dll"))
                {
                    try
                    {
                        Assembly assembly = Assembly.LoadFrom(file);
                        Type[] types = assembly.GetTypes();
                        foreach (Type type in types)
                        {
                            if (type.BaseType.FullName == "PlugInBase.Base")
                            {
                                object obj = assembly.CreateInstance(type.FullName);
                                string name = type.GetMethod("Name").Invoke(obj, null).ToString();
                                string desc = type.GetMethod("Desc").Invoke(obj, null).ToString();
                                string version = type.GetProperty("Version").GetValue(obj).ToString();

                                List.Add(new PluginModel
                                {
                                    Name = name,
                                    Desc = desc,
                                    Version = version,
                                    type = type,
                                    Obj = obj
                                });
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        throw ex;
                    }
                }
            }

            dgrvPlugins.DataSource = new BindingList<PluginModel>(List);
        }

        /// <summary>
        /// 開啟外掛目錄
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btOpenPluginDir_Click(object sender, EventArgs e)
        {
            Process.Start(PlugInPath);
        }


        /// <summary>
        /// 執行選中外掛
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btExcute_Click(object sender, EventArgs e)
        {
            //獲取選擇的外掛資訊
            int index = dgrvPlugins.CurrentRow.Index;
            object obj = List[index].Obj;
            Type type = List[index].type;
            //引數
            object[] inParams = new object[2];
            inParams[0] =Convert.ToInt32( dgrvPlugins.CurrentRow.Cells[2].Value);
            inParams[1] = Convert.ToInt32(dgrvPlugins.CurrentRow.Cells[3].Value);
            object value = type.GetMethod("Run").Invoke(obj, inParams);
            MessageBox.Show(Convert.ToString(value),"結果",MessageBoxButtons.OK);
        }
    }

專案設定

外掛生成設定

編譯生成專案的時候需要注意,此處的呼叫外掛是通過反射呼叫.dll中類和方法,所以首先要找到這個.dll的檔案,所以此處我們在Winform使用者端程式下建立一個存放類庫dll的檔案PlugIns,在外掛類庫專案生成後事件命令中,填入如下命令:

copy /Y "$(TargetDir)$(ProjectName).dll" "$(SolutionDir)\PlugIns"

以上命令代表,在專案的類庫生成後,將類庫copy到解決方案的路徑子資料夾PlugIns,也就是我們建立存放自定義外掛的資料夾。當然,如果不怕麻煩,每次生成後,手動複製到此資料夾也可以,直接複製到使用者端程式的..\bin\PlugIns資料夾下。

外掛路徑設定

全選這些類庫,把這些類庫設定為"如果較新則複製",這樣每次在編譯使用者端程式,如果自定義外掛有更新,則同步會複製到bin目錄下

外掛基礎類別設定

外掛基礎類別提供了規範,需要在類庫的生成後事件,新增命令:

copy /Y "$(TargetDir)$(ProjectName).dll" "$(SolutionDir)\bin\Debug"

將生成的dll檔案,拷貝到使用者端程式的bin路徑下

呼叫演示

CustomPlugInA

CustomPlugInB

CustomPlugInC

CustomPlugInD

外掛開發優缺點

  • 把擴充套件功能從框架中剝離出來,降低了框架的複雜度,讓框架更容易實現
  • 宿主中可以對各個模組解析,完成外掛間、外掛和主程式間的通訊。
  • 外掛開發的可延伸性,靈活性比較高,而且可以進行客製化化開發。
缺點
  • 每一個外掛被編譯成了dll,各模組無法單獨執行,必須依託於主程式。

  • 修改外掛時,由於生成的是dll,無法快速直觀的檢視修改以及偵錯。

  • 每一個外掛必須依賴於某一個規範。