码迷,mamicode.com
首页 > 其他好文 > 详细

一个插件程序的制作过程(一)

时间:2016-07-20 13:16:45      阅读:125      评论:0      收藏:0      [点我收藏+]

标签:

0x00 前言

其实做插件相关的程序是我梦寐以求的东西,很早我就开始设想,但是那时候还不会C#,C++又比较晦涩难懂,微软的组件技术还停留在COM+的水平上,自己设计插件系统的学习成本实在太大。所以就一直憋着憋着。现在上手学C#才发现,反射是一个好用的东西,它他适合做组件化程序了。

 

于是我就开始做了一个简单的Demo。

  1     class Program
  2     {
  3         static string[] _methods;
  4         static string _PluginName;
  5         static string _PluginScribe;
  6         static bool t = true;
  7         static int errorcount = 0;
  8         static Assembly assemble;
  9         static Type Package;
 10         static object dll;
 11         static object[] obj;
 12         /*
 13          * 对于FileName变量
 14          * 
 15          * 如果是调试模式下,请直接将它的值设定为测试使用的程序集
 16          * 如果是发行模式下,请赋值""
 17          * 
 18          * 对于count变量
 19          * 
 20          * 如果是调试模式,请输入1
 21          * 如果是发行模式,请设置为0
 22          * 
 23          * 对于插件而言,每个插件都必须存在静态变量
 24          *   Methods  PluginName  PluginScribe
 25          * Methods变量为string[]数组,需要保存当前插件内部所有的方法
 26          * PluginName变量解释了当前插件的名称
 27          * PluginScribe变量解释了当前插件的作用以及用途
 28          */
 29         static void Main(string[] args)
 30         {
 31             Title = "NtToolPlatform";//设置标题
 32 
 33             string FileName = @"F:\项目\ClassLibrary1\Package\bin\Debug\Package.dll";
 34             int count = 1;//计数器,每存在一个不是文件路径的参数则计数一次
 35            
 36             foreach (string var in args) //遍历参数,寻找程序集的文件路径
 37             {
 38                 if (System.IO.File.Exists(var) == true)//所有参数之中有文件存在并且后缀名为dll时不添加计数器
 39                 {
 40                     
 41                     System.IO.FileInfo fi = new System.IO.FileInfo(var);
 42                     if (fi.Extension == "dll")
 43                     {
 44                         FileName = var;
 45                     }
 46                 }
 47                 else
 48                 {
 49                     count++;
 50                 }
 51             }//遍历完成,判断是不是所有参数都不携带拓展插件
 52             
 53             if (count == args.Length)
 54             {
 55                 //证明没有拓展插件
 56                 System.Windows.Forms.MessageBox.Show("运行参数不正确", "错误",
 57                     System.Windows.Forms.MessageBoxButtons.OK,
 58                     System.Windows.Forms.MessageBoxIcon.Error);
 59                 //弹出错误提示框
 60             }
 61             else
 62             {
 63                 //加载插件
 64                 assemble = Assembly.LoadFile(FileName);
 65                 //-------------------------------------------
 66                 //创建实例
 67                 //TODO :创建实例并不等于初始化
 68                 //-------------------------------------------
 69                 dll = assemble.CreateInstance("Package.Package");
 70                 Package = dll.GetType();//获得元数据的类型
 71                 
 72                 FieldInfo f = Package.GetField("Methods");
 73                 FieldInfo d = Package.GetField("PluginName");
 74                 FieldInfo scribe = Package.GetField("PluginScribe");
 75                 
 76                 obj = new object[2] 
 77                 {
 78                     new Func<string>(() => { return ReadLine(); }),
 79                     new Action<string>((msg) =>{ Write(msg); } )            
 80                 };//将Action<string>委托装箱操作
 81                 //=================================================
 82                 //=================================================
 83                 if (f == null || d == null)
 84                 {
 85                     //如果f为null则表示这个插件是非法插件,无法调用
 86                     System.Windows.Forms.MessageBox.Show("该插件为非法插件无法调用", "错误",
 87                     System.Windows.Forms.MessageBoxButtons.OK,
 88                     System.Windows.Forms.MessageBoxIcon.Error);
 89                 }
 90                 else
 91                 {
 92                     //获得方法列表及相关的文本信息
 93                     _methods = (string[])f.GetValue(dll);
 94                     _PluginName = (string)d.GetValue(dll);
 95                     _PluginScribe = (string)scribe.GetValue(dll);
 96 
 97                     Start();
 98 
 99                     while (t == true)
100                     {
101                         Write("Command\t>");
102                         string msg = "";
103                         
104                         msg = ReadLine();
105                         
106                         Check(msg);//判断
107 
108                     }
109                 }
110             }
111         }
112         /// <summary>
113         /// 封装读取文本的命令
114         /// </summary>
115         /// <returns>返回读取到的文本</returns>
116 
117         /// <summary>
118         /// 检查输入的文本是否为命令
119         /// </summary>
120         /// <param name="Command">传入输入的文本</param>
121         static void Check(string Command)
122         {
123             /*
124              * [插件名称]? 命令 导出所有方法及其解释文本
125              * [方法名称]? 命令 导出指定方法的参数信息及说明文本
126              * Back  命令  返回上一层
127              * Exit  命令  退出程序
128              */
129             string s = Command.ToUpper();
130             if (s == "EXIT")
131             {
132                 //退出命令
133                 t = false;
134             }
135             else if (Command == string.Empty)
136             {
137                 //空白命令
138                 Write("Command\t>");
139             }
140             else if (s == _PluginName.ToUpper() + "?" || s == _PluginName.ToUpper())
141             {
142                 WriteLine(_PluginScribe);
143             }
144             else if (s == "CLS" || s == "CLEAR")
145             {
146                 //清屏函数
147                 Clear();
148             }
149             else if (s == "?" || s == "")
150             {
151                 //帮助命令
152                 Help();
153             }
154             else
155             {
156                 //判断是不是插件命令
157                 MethodInfo mi = null;
158                 bool l = false;
159                 foreach (string c in _methods)
160                 {
161                     if (c.ToUpper() == s)
162                     {
163                         l = true;
164                         mi = Package.GetMethod(c);
165                     }
166                 }
167                 if (l == true)
168                 {
169                     mi.Invoke(dll,obj);
170                 }
171                 else
172                 {
173                     //如果是无法识别的命令
174                     //添加一次错误计数
175                     if (errorcount >= 2)
176                     {
177                         //如果连续错误大于两次则提示返回上一层菜单或者显示帮助
178                         WriteLine("\n‘{0}‘不是命令\n输入?显示帮助", Command);
179                         Reset();
180                     }
181                     else
182                     {
183                         errorcount++;
184                         WriteLine("‘{0}‘不是命令", Command);
185                     }
186                 }
187             }
188         }
189         /// <summary>
190         /// 控制台顶部提示文本
191         /// </summary>
192         static void Start()
193         {
194             WriteLine("NtToolPlatForm[版本 V1.0.0]\n作者 Luo (C)保留该作品的所有权利\n查看提示\n");
195         }
196         /// <summary>
197         /// 清屏函数
198         /// </summary>
199         static void Clear()
200         {
201             Console.Clear();
202             Start();
203             errorcount = 0;
204         }
205         /// <summary>
206         /// 错误计数器重设
207         /// </summary>
208         static void Reset()
209         {
210             errorcount = 0;
211         }
212         static void Help()
213         {
214             WriteLine("");
215             //主体
216             Write("[打开的程序集名称]\tPackage.Package\n[插件名称]\t{0}\n", _PluginName);
217             WriteLine("\t插件名称?\t\t导出所有方法及其解释文本\n");
218             //平台命令
219             Write("[平台命令]\n");
220             Write("\tcls\t\t\t清空屏幕缓冲区\n\texit\t\t\t退出命令\n\t?\\tt\t帮助命令\n\tBack\t\t\t返回上一层\n");
221             WriteLine("");
222             //插件命令
223             WriteLine("[插件命令]");
224             WriteLine("\t方法名称?\t\t\t导出指定方法的参数信息及说明文本");
225             WriteLine("\t方法名称\t\t\t进入对应方法操作界面");
226             //
227             WriteLine("");
228         }

这是一个控制台的代码,是实现主体,如果大家想要编译,请记得引用System.Windows.Forms这个命名空间,还要加入引用。因为控制台没有比较强势的提示工具,所以还是用Winform的MessageBox比较好一些。因为涉及外部调用,需要一个正确的打开方式,所以才设计这个。

平台用的是VS2015,所以我using static System.Console了。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Threading.Tasks;

namespace Package
{
    /// <summary>
    /// Package类中保存着所有方法
    /// </summary>
    public class Package
    {
        /// <summary>
        /// 保存当前插件所有方法
        /// </summary>
        public static string[] Methods = new string[2] { "DnsGetHostAddress", "DnsGetHostName" };
        /// <summary>
        /// 保存当前插件名字
        /// </summary>
        public static string PluginName = "NetTool";
        /// <summary>
        /// 保存插件的说明文本
        /// </summary>
        public static string PluginScribe = "该插件为了方便开发者使用网络编程而设计,能够在非调试环境下查看自己的代码是否会发生错误";
        /// <summary>
        /// 初始化Package类,并注册所有方法
        /// </summary>
        public Package()
        {

        }
        /// <summary>
        /// 调用Dns.GetHostAddress方法
        /// </summary>
        /// <remarks>对于Action的委托最好传入参数为WriteLine</remarks>
        public void DnsGetHostAddress(Func<string> Read, Action<string> Write)
        {
            Write.Invoke("\nDns.GetHostAddress(string hostNameOrAddress)\n\n请输入代表主机名字或者主机ip的文本:");
            //获得所有的IP地址
            try
            {
                IPAddress[] ip = Dns.GetHostAddresses(Read.Invoke());
                Write.Invoke("\n[结果]\n");
                foreach (IPAddress var in ip)
                {
                    Write.Invoke(var.ToString() + "\n");
                }
            }
            catch (Exception e)
            {
                Write.Invoke(
                    "\n[错误代码]\t" + e.HResult.ToString() + "\n[错误提示]\t" + e.Message + "\n");
            }
            //return temp;
        }
        /// <summary>
        /// 获得主机名
        /// </summary>
        /// <param name="Method"></param>
        public void DnsGetHostName(Action<string> Method)
        {
            Method.Invoke(Dns.GetHostName());
        }
    }
}

这是插件部分的代码。一开始的实现就是这样子,不过还有很多需要考虑的问题。

0x01思考

对于一个插件系统而言,最重要的就是它们之间的约定,我做这个插件系统,主要想要实现的功能就是导入插件,每个插件具有不一样的方法,然后使用反射动态绑定。当我需要使用某个方法的时候我输入这个方法名称,然后让我设计的插件平台去搜索程序集内部,如果存在该方法则调用。虽然设想比较简单,但是实际操作的时候我发现之中需要很多的共通设计,也就是说,一个外部程序集,必须符合某些约定。这些约定多了就要设计规范。如果你想要做成一个庞大的插件系统的话,那更需要科学的设计。

所以,有些项目很适合锻炼一个人的统筹思维。

这是题外话,这里我们主要讨论四个东西:抽象类,静态类,接口以及单纯的约定,它们是我们主要考虑的主要约定实现方法。

抽象类大部分由虚函数构成,但是要注意,抽象类实际上还是可以实现方法的。例如:

    public abstract class NtEngine
    {
        public virtual void Load()
        {

        }

        public virtual void Exports()
        {

        }
        public void A()
        {

        }
    }
}

但是这样的方法是不能继承的,一般而言,你可以在虚函数里面写代码实现,然后继承的时候Override可以实现重写,也可以进行功能拓展。

这里要强调的主要是抽象类、接口、静态类。

0x02抽象类

抽象类因为不能实例化,所以必须引用一个实体或者被继承,因此抽象类适合作为插件系统的内核。

技术分享

由这幅图大概就能表示我的想法了,抽象类以一个插件实体为核心,这样就可以找到实现方法的客体了。但是这里就有一个思考了,因为抽象类强调继承,他更希望自己是一个基类,其他类都是他的派生类。使用抽象类构造插件系统,那么这个插件程序集里面就必须继承并实现抽象类主体的方法,我就来搞笑一发吧。

VS里面一个项目只会生成一个DLL,如果你要把插件打包成单独的DLL需要自己新建一个项目,然后将共通的内容逐一导入。

 

这个思考还是小问题,最主要的是反射的时候,创建实例的时候命名空间的问题,假设你不知道这个插件内部有什么命名空间,这时候你该怎么寻找?抽象类是一个强调单继承的对象,如果不按照规范开发的话,很容易导致插件不能使用(具体是不是我还没试过)

 

技术分享

 

一个插件程序的制作过程(一)

标签:

原文地址:http://www.cnblogs.com/Danfish/p/5687893.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!