C#插件构架实战

转载 2007年10月12日 16:14:00
转自http://dev.csdn.net/article/31/31749.shtm,好文章,转
一、引言
    1. 问题的引入
    假设你设计的程序已经部署到用户的计算机上,并且能够正常运行了。但是有一天,用户打来了电话——他们要求增加新的功能。确定了用户的需求后,你竟然发现原有的软件架构已经无法胜任新增任务的需求——你需要重新设计这个应用了!但问题是,就算你又用了一个开发周期完成了用户需要的应用,却不能保证用户的需求不会再次变更。也就是说,需求蔓延的可能性依然存在。因此,这种情况下插件构架更能显示出它的优越性。
    2. 几个解决方案的对比
    我总结了一下我所接触到的插件构架,大致上可分为以下几类:
i> 脚本式
    使用某种语言把插件的程序逻辑写成脚本代码。而这种语言可以是 Python ,或是其他现存的已经经过用户长时间考验的脚本语言。甚至,你可以自行设计一种脚本语言来配合你程序的特殊需要。当然,用当今最流行的 XML 是再合适不过了。
    这种形式的特点在于,稍有点编程知识的用户就可以自行修改你的脚本( ^_^ 假如你不加密它的话)。我们无法论证这是好处还是坏处。因为,这种情况所造成的后果是不可预知的。
ii> 动态函数库 DLL
    插件功能以动态库函数的形式存在。主程序通过某种渠道(插件编写者或某些工具)获得插件 DLL 中的函数签名,然后在合适的地方调用它们。用过 Matlab 的读者都知道, Matlab 中的各项功能几乎都是些动态链入的函数。
iii> 聚合式
    顾名思义,就是把插件功能直接写成 EXE 。主程序除了完成自己的职责外,还负责调度这些“插件”。我不喜欢这种形式。这使插件与插件之间,主程序与插件之间(主要是这一点)的信息交流困难了许多。巴比伦塔的失败 [1] 从某种程度上讲就是信息交流无法实现造成的。
iv> COM 组件
    COM [2] 的产生给这个世界增添了几分活力。只有接口!我们的插件需要做的只是实现程序定义的接口。主程序不需要知道插件怎样实现预定的功能,它只需要通过接口访问插件,并提供主程序相关对象的接口。这样一来,主程序与各插件之间的信息交流就变得异常简单。并且,插件对于主程序来说是完全透明的。
    3. 决策
    C# 是面向对象的程序设计语言。它提供了 interface 关键字来直接定义接口。同时, System.Reflection 命名空间也提供了访问外部程序集的一系列相关对象。这就为我们在 C# 中实现插件构架打下了坚实的基础。
    下面,我们将以一个具有插件构架的程序编辑器为例,来阐述这种构架在 C# 中的实现。
二、设计过程
    好了,现在我们准备把所有的核心代码都放在 CSPluginKernel 命名空间中。用VSIDE建立一个C#类库工程。在命名空间 CSPluginKernel 中开始我们的代码。
    1. 接口设计
    我们的程序编辑器会向插件开放正在编辑的文档对象。程序启动后,就枚举每一个插件并把它连接到主程序,同时传递主程序对象的接口。插件可以通过这个接口来请求主程序对象或访问主程序功能 。
    根据上面的需求,我们首先需要一个主程序接口:
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 ) );
     现在,你就可以撰写测试代码了。
三、源代码
     由于篇幅所限,完整的源代码(包含测试用例)请在下面的链接下载。下载后请用 VS.NET2003 打开,重新生成解决方案即可(需要 .NET Framework 1.1)。测试用例是一个在 RichTextBox 控件里插入红色文本的插件。很简单,只作测试之用。
四、结语
     That's all! 有了这种插件构架,可怜的程序员们就再也不用为需求蔓延耗费心机了。另外,欢迎对本文以及本文的附加代码作出评价。还有,就是,常去我的 Blog 看看~~ ^_^
注:
[1] 巴比伦塔的失败   《人月神话》Frederick P. Brooks Jr.  第 7 章 为什么巴比伦塔会失败
[2] COM   有关 COM/COM+ 的详细技术细节请参见《 Mastering COM and COM+ 》 , Ash Rofail , Yasser Shohoud.
 

C# 插件构架实战

一、引言     1. 问题的引入     假设你设计的程序已经部署到用户的计算机上,并且能够正常运行了。但是有一天,用户打来了电话——他们要求增加新的功能。确定了用户的需求后,你竟然发现原有的软...
  • yin554393109
  • yin554393109
  • 2013年03月06日 16:47
  • 316

改进的C#插件构架

不过这种技术接口的制订是个难题,设计不好很影响以后的功能扩展 —— fking 比较简单的插件想法,扩展的功能是有限的。 应该考虑主程序本身也应该是一个插件的结构。也就是说插件分为宿主插件和扩展...
  • u013566878
  • u013566878
  • 2014年03月28日 01:45
  • 439

4_C# 实现VMS客户端——窗体架构设计

声明: 本博客为原创博客,主要讲述使用C#语言调用服务端SDK方式完成VMS客户端完整功能实现,转载请声明出处。 如有技术问题或需交流可直接联系本人邮箱:chuiwenwei@163.co...
  • chuiwenwei_csdn
  • chuiwenwei_csdn
  • 2014年09月09日 14:41
  • 694

C# 插件开发学习实例

最近在看蒋波涛先生的插件式GIS开发的书。由于对插件原理不懂。进展不是很顺利。看了某个教学视频关于插件的介绍,略懂,写下此文,记录。 以下是模拟一个记事本,将其中的格式用插件的形式实现。 STEP 1...
  • myyouthlife
  • myyouthlife
  • 2014年01月06日 15:35
  • 1335

C# 线程池和编程实例

C# 线程池和编程实例 一      线程池可以看做容纳线程的容器;     一个应用程序最多只能有一个线程池;     ThreadPool静态类通过QueueUserWorkItem()方法将工作...
  • bcbobo21cn
  • bcbobo21cn
  • 2015年05月09日 23:29
  • 791

初识OSGI.NET插件框架

OSGI全称为Open Service Gateway Initiative,OSGI是一个开放服务规范,基于该平台可以使得很多人来共同使用和协作。        基于OSGI.NET的插件框架提供了...
  • liutengteng130
  • liutengteng130
  • 2014年01月01日 20:48
  • 8767

C#实现插件式架构的方法

原文:http://www.renfb.com/blog/2011/Article/259 插件式架构,一种全新的、开放性的、高扩展性的架构体系.插件式架构设计近年来非常流行,基于插件的设计...
  • Joyhen
  • Joyhen
  • 2014年04月04日 02:40
  • 4700

插件式构架设计经验

1  只建议导出接口或者函数,使用标准类型变量,不建议导出复杂的类。 解释: 插件式编程就是最大化的解耦,模块全部放置于DLL中,并且每个DLL只导出标准的加载函数,由加载函数再次导出模块类接...
  • coyer
  • coyer
  • 2013年05月17日 11:14
  • 558

【C#】开发插件式程序简单例程

相信各位程序猿们都不会对插件太陌生,无论是firefox还是chrome还是其他的什么程序,插件都占到举足轻重的作用,是对程序功能的一种扩展。反正就是很有用的东西啦,可以很方便地扩展自己的程序。本渣最...
  • mokeyjay
  • mokeyjay
  • 2014年04月03日 16:57
  • 1010

C# Window编程随记——第三方读取Excel表格数据的插件(NPOI.dll)

C# Window编程随记——第三方读取Excel表格数据的插件(NPOI.dll) NPOI插件简介 下载NPOI.dll插件 将插件导入C#工程 写一个操作类用于插件的相关操作 1.NPOI插件简...
  • linshuhe1
  • linshuhe1
  • 2015年10月21日 11:51
  • 3351
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:C#插件构架实战
举报原因:
原因补充:

(最多只允许输入30个字)