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

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需要自己新建一个项目,然后将共通的内容逐一导入。

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

时间: 2024-10-04 16:40:12

一个插件程序的制作过程(一)的相关文章

一个Java程序的执行过程(转)

我们手工执行java程序是这样的: 1.在记事本中或者是UE的文本编辑器中,写好源程序: 2.使用javac命令把源程序编译成.class文件: 编译后的.class(类字节码)文件中会包含以下内容: ConstantPool:符号表: FieldInfo:类中的成员变量信息: MethodInfo:类中的方法描述: Attribute:可选的附加节点. FieldInfo节点包含成员变量的名称,诸如public,private,static等的标志.ConstantValue属性用来存储静态的

一个C#程序的执行过程

可能很多人都知道我们把程序打包成dll就丢出去了,但是里面的具体的执行过程是怎么样的呢. 程序集是由元数据和IL组成的.IL是和CPU无关的语言,是微软的几个专家请教了外面的编译器的作则,开发出来的.IL比大多数机器语言都要高级一点.IL能够访问和操作对象类型,并提高了指令来初始化对象,调用对象上的虚方法以及直接操作数组元素. 比如下面这个例子 class Program { static void Main(string[] args) { Console.WriteLine("Hello&q

【Android开发】找乐,一个笑话App的制作过程记录

缘起 想做一个笑话App的原因是由于在知乎上看过一个帖子.做Android能够有哪些数据能够练手,里面推荐了几个数据开放平台. 在这些平台中无一不是有公共的笑话接口,当时心想这个能够拿来练手啊,还挺有意思的,预计还能积累一点用户. 碰巧(真的好巧)在Github中遇到了一个MVP设计模式的框架Beam,作者Jude95有一个笑话仓库----Joy(豆逼).就是一个做笑话的! 更巧的是用到的接口也是我在关注的接口.心想不如改造一下吧,做个升级版.自己也能够在这个中学到别人是怎么写App的. 后来发

微信小程序免费制作一键生成平台是什么原理?速成应用代理需要多少钱

微信10亿活跃用户,10亿的流量等待瓜分.想想现在人们用哪个应用最多?当然是微信,而微信小程序就是依托微信而存在的,先天优势就在那里,怎么可能不火?作为想要创业以及苦于一直没有机会发现好商机的你,为什么要错过速成应用微信小程序加盟代理这个项目? 小程序是一种不需要下载安装即可使用的应用,它实现了应用"触手可及"的梦想,用户扫一扫或者搜一下即可打开应用.也体现了"用完即走"的理念,用户不用关心是否安装太多应用的问题.应用将无处不在,随时可用,但又无需安装卸载.全面开放

第一个AAuto程序

第一个程序都是从hello world开始的.~ 启动快手,点击"快手主菜单 -> 新建工程 -> 对话框应用程序" 打开创建工程的对话框. 显示的工程向导如下图: 在上图的对话框界面中直接点击创建工程按钮 - 创建一个工程. 在左侧找到[工程管理器],点击[工程根目录], 然后点击工程管理器顶部左侧第一个按钮[显示工程属性 ] 可在[属性面板]修改工程属性.如下图: 修改属性项以后,鼠标单击其他属性以完成输入. 我们试试把工程名字改为"我的工程",可以

[PCB制作] 1、记录一个简单的电路板的制作过程——四线二项步进电机驱动模块(L6219)

前言 现在,很多人手上都有一两个电子设备,但是却很少有人清楚其中比较关键的部分(PCB电路板)是如何制作出来的.我虽然懂点硬件,但是之前设计的简单系统都是自己在万能板上用导线自己焊接的(如下图左),复杂的都是模块拼接的(如下图右):      工作中原理图和PCB也有专门的工程师来制作,因此我对这一块了解比较少.而最近闲来无事,又因为手头上确实少一个四线二项步进电机驱动模块.起初是在淘宝上找了很久才找到一个适合的,结果实验了一下午还是不行:又考虑自己在万能板上焊接,可是发现该模块外围需要10个左

WordPress插件制作教程(一): 如何创建一个插件

上一篇还是按照之前的教程流程,写了一篇WordPress插件制作教程概述,从这一篇开始就为大家具体讲解WordPress插件制作的内容.这一篇主要说一下插件的创建方法. 相信大家都知道插件的安装文件在什么地方吧,没错就在WP-Content->plugins里面,我们所安装的插件都存放在了这个文件夹里面.当我们刚开始搭建好WordPress网站的时候,里面会默认提供两个插件,一个是Akismet(过滤垃圾评论插件)和一个hello插件(显示歌词的插件).我们可以打开hello.php这个文件,这

一个应用程序无法启动错误的解决过程

作者:朱金灿 来源:http://blog.csdn.net/clever101 早上同事向我请教一个问题,说是启动exe时遇到一个应用程序无法启动的错误,具体例如以下图: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" align="middle" /> 我让他打开"控制

“我的个人志”制作过程

我的个人志: 2015.12.17记 制作过程:暂分为四个部分:第一页: 第一页:(主页君,INDEX):内容:视频,图画,新闻热点第二页:(趣点屋,SHARE-life-): 第三页:(时光廊,PHOTO-wall):内容:个人生活照,历史小故事,意境,我欣赏,自己制作第四页:(绣我秀,ABOUT-me):内容:个人简介. 初步大概制定时间:2015.10.08,整理于10.09 网页内容css框架类似于W3C的样子 404页面效果: 学习和收集资料中,资料还太少了,整个结构内容板块还没有定下