Visual C# 插件构架实战

2016-02-19 17:08 30 1 收藏

今天图老师小编给大家精心推荐个Visual C# 插件构架实战教程,一起来看看过程究竟如何进行吧!喜欢还请点个赞哦~

【 tulaoshi.com - 编程语言 】

  一、引言

  1. 问题的引入

  假设你设计的程序已经部署到用户的计算机上,并且能够正常运行了。但是有一天,用户打来了电话他们要求增加新的功能。确定了用户的需求后,你竟然发现原有的软件架构已经无法胜任新增任务的需求你需要重新设计这个应用了!但问题是,就算你又用了一个开发周期完成了用户需要的应用,却不能保证用户的需求不会再次变更。也就是说,需求蔓延的可能性依然存在。因此,这种情况下插件构架更能显示出它的优越性。

  2. 几个解决方案的对比

  我总结了一下我所接触到的插件构架,大致上可分为以下几类:

  i 脚本式

  使用某种语言把插件的程序逻辑写成脚本代码。而这种语言可以是 Python ,或是其他现存的已经经过用户长时间考验的脚本语言。甚至,你可以自行设计一种脚本语言来配合你程序的特殊需要。当然,用当今最流行的 XML 是再合适不过了。

  这种形式的特点在于,稍有点编程知识的用户就可以自行修改你的脚本( ^_^ 假如你不加密它的话)。我们无法论证这是好处还是坏处。因为,这种情况所造成的后果是不可预知的。

  ii 动态函数库 DLL

  插件功能以动态库函数的形式存在。主程序通过某种渠道(插件编写者或某些工具)获得插件 DLL 中的函数签名,然后在合适的地方调用它们。用过 Matlab 的读者都知道, Matlab 中的各项功能几乎都是些动态链入的函数。

  iii 聚合式

  顾名思义,就是把插件功能直接写成 EXE 。主程序除了完成自己的职责外,还负责调度这些插件。我不喜欢这种形式。这使插件与插件之间,主程序与插件之间(主要是这一点)的信息交流困难了许多。巴比伦塔的失败 [1] 从某种程度上讲就是信息交流无法实现造成的。

  iv COM 组件

  COM [2] 的产生给这个世界增添了几分活力。只有接口!我们的插件需要做的只是实现程序定义的接口。主程序不需要知道插件怎样实现预定的功能,它只需要通过接口访问插件,并提供主程序相关对象的接口。这样一来,主程序与各插件之间的信息交流就变得异常简单。并且,插件对于主程序来说是完全透明的。

  3. 决策

  C# 是面向对象的程序设计语言。它提供了 interface 关键字来直接定义接口。同时, System.Reflection 命名空间也提供了访问外部程序集的一系列相关对象。这就为我们在 C# 中实现插件构架打下了坚实的基础。

(本文来源于图老师网站,更多请访问http://www.tulaoshi.com/bianchengyuyan/)

  下面,我们将以一个具有插件构架的程序编辑器为例,来阐述这种构架在 C# 中的实现。

  二、设计过程

  好了,现在我们准备把所有的核心代码都放在 CSPluginKernel 命名空间中。用VSIDE建立一个C#类库工程。在命名空间 CSPluginKernel 中开始我们的代码。

  1. 接口设计

  我们的程序编辑器会向插件开放正在编辑的文档对象。程序启动后,就枚举每一个插件并把它连接到主程序,同时传递主程序对象的接口。插件可以通过这个接口来请求主程序对象或访问主程序功能 。

  根据上面的需求,我们首先需要一个主程序接口:

(本文来源于图老师网站,更多请访问http://www.tulaoshi.com/bianchengyuyan/)

  

public interface IApplicationObject {    void Alert( string msg ); // 产生一条信息    void ShowInStatusBar( string msg ); // 将指定的信息显示在状态栏    IDocumentObject QueryCurrentDocument(); // 获取当前使用的文档对象    IDocumentObject[] QueryDocuments(); // 获取所有的文档对象    // 设置事件处理器    void SetDelegate( Delegates whichOne , EventHandler targer );}// 目前只需要这一个事件public enum Delegates {    Delegate_ActiveDocumentChanged ,}     然后是 IDocumentObject 接口。插件通过这个接口访问编辑器对象。////// 编辑器对象必须实现这个接口///public interface IDocumentObject {    // 这些属性是 RichTextBox 控件的相应的属性映射    string SelectionText { get ; set ; }    Color SelectionColor { get ; set ; }    Font SelectionFont { get ; set ; }    int SelectionStart { get ; set ; }    int SelectionLength { get ; set ; }    string SelectionRTF { get ; set ; }    bool HasChanges { get ; }    void Select( int start , int length );    void AppendText( string str );    void SaveFile( string fileName );    void SaveFile();    void OpenFile( string fileName );    void CloseFile();}

  这个接口不需要过多解释。这里我只实现了RichTextBox控件少数的几个方法,其他可能用得到的,读者自行添加即可。

  再然后,根据插件在其生命周期里的行为,设计插件的接口。

  

////// 本程序的插件必须实现这个接口///public interface IPlugin {    ConnectionResult Connect( IApplicationObject app );    void OnDestory();    void OnLoad();    void Run();}////// 表示插件与主程序连接的结果///public enum ConnectionResult {    Connection_Success ,    Connection_Failed}

  主程序会首先调用 Connect() 方法,并传递 IApplicationObject 给插件。插件在这个过程中做一些初始化工作。然后,插件的 OnLoad() 方法被调用。在这之后,当主程序接收到调用插件的信号时(键盘、鼠标响应)就会调用插件的 Run() 方法来启动这个插件。程序结束时,调用其 OnDestory() 方法。这样,插件的生命才宣告结束

  2. 插件信息的存储与获取

  一个插件需要有它的名称 、版本等信息。作为设计者的你,也一定要留下你的尊姓大名和个人网站等用来宣传自己。 C# 的新特性属性, 就是一个很好的解决方案。因此我们定义一个从 System.Attribute 继承来的类 PluginInfoArrtibute :

  

////// 用来指定一个插件的相关信息///public class PluginInfoAttribute : System.Attribute{    ///    /// Deprecated. Do not use.    ///    public PluginInfoAttribute() {}    public PluginInfoAttribute(     string name , string version ,     string author , string webpage , bool loadWhenStart ) {     // 细节已略去    }    public string Name { get { return _Name; } }    public string Version { get { return _Version; } }    public string Author { get { return _Author; } }    public string Webpage { get { return _Webpage; } }    public bool LoadWhenStart { get { return _LoadWhenStart; } }    ///    /// 用来存储一些有用的信息    ///    public object Tag {     get { return _Tag; }     set { _Tag = value ; }    }    ///    /// 用来存储序号    ///    public int Index {     get { return _Index; }     set { _Index = value ; }    }    private string _Name = "";    private string _Version = "";    private string _Author = "";    private string _Webpage = "";    private object _Tag = null ;    private int _Index = 0;    // 暂时不会用    private bool _LoadWhenStart = true ;}     用这个类修饰你的插件,并让他实现 IPlugin 接口:////// My Pluging 1( Just for test )///[    PluginInfo("My Pluging 1( Just for test )" ,"1.0" , "Jack H Hansen" ,    "http://blog.csdn.net/matrix2003b" , true )]public class MyPlugin1 : IPlugin {    public MyPlugin1() { }    #region IPlugin 成员    // 细节已略去    #endregion    private IApplicationObject _App;    private IDocumentObject _CurDoc;}

  3. 加载插件

  现在就得用到 System.Refelction 命名空间了。程序在启动时会搜索 plugins 目录下的每一个文件。对于每一个文件,如果它是一个插件,就用 Assembly 对象加载它。然后枚举程序集中的每一个对象。判断一个程序集是否为我们的插件的方法是判断它是否直接或间接实现自 IPlugin。用下面的函数,传递从程序集枚举的对象的System.Type。

  

private bool IsValidPlugin( Type t ) {    bool ret = false ;    Type[] interfaces = t.GetInterfaces();    foreach ( Type theInterface in interfaces ) {     if ( theInterface.FullName == "CSPluginKernel.IPlugin" ) {      ret = true ;      break ;     }    }    return ret;}

  若条件都满足,IsValidPlugin() 就会返回 true 。接着程序就会创建这个对象并把它存于一个 ArrayList 中。

  plugins.Add( pluginAssembly.CreateInstance( plugingType.FullName ) );

  现在,你就可以撰写测试代码了。

来源:http://www.tulaoshi.com/n/20160219/1614304.html

延伸阅读
作为软件设计和开发人员大都有过使用DLL(动态连接库)的经历,DLL的产生使得我们的应用程序在可维护性、代码的重复使用等方面都有了很大的提高。以前用的DLL一般都是用Visual C++、Delphi或者VB等编程语言来编写的,这种DLL的编写和使用,我们大都已经比较习惯了。作为新一代的程序开发语言--Visual C#,到底是如何编写和使用DLL的呢!本...
程序的活动是通过语句(statement)来表达的。C#支持几种不同的语句,许多语句是以嵌入语句的形式定义的。 块(block)允许在只能使用单个语句的上下文中编写多个语句。块由一个括在大括号{}内的语句列表组成。 声明语句(declaration statement)用于声明局部变量和常量。 表达式语句(expression statement)用于运算表达...
前一篇《Visual C#.Net网络程序开发之Socket》中说到:支持Http、Tcp和Udp的类组成了TCP/IP三层模型(请求响应层、应用协议层、传输层)的中间层-应用协议层,该层的类比位于最底层的Socket类提供了更高层次的抽象,它们封装 TCP 和 UDP 套接字的创建,不需要处理连接的细节,这使得我们在编写套接字级别的协议时,可以更多地尝试使用 TCPCli...
前段时间因为项目需要,做了个用来对数组排序的类,顺便把以前学过的几种排序算法用C#实现一下。用C#的一些机制来诠释了一下算法的是实现。在阅读本之前,需要一些对C#的有些基本的了解,了解方法参数中out ,ref的作用,掌握面向对象的一些基本思想。 1. 插入排序 1.1. 基本思想: 每次将一个待排序的数据元素,插入到前面...
推荐: Visual C# 轻松入门全攻略 匿名方法基础 匿名方法是C#2.0的一个新的语言特性。本文的主要内容是提供给读者关于匿名方法的内部实现和工作方式的一个更好的理解。本文无意于成为匿名方法的完全语言特性参考。 匿名方法允许我们定义委托对象可以接受的代码块。这个功能省去我们创建委托时想要传递给一个...

经验教程

575

收藏

69
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部