C# 插件构架实战(Jack H Hansen )

一、引言

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.

转载于:https://www.cnblogs.com/smallfa/archive/2008/04/14/1152380.html

C# 插件构架实战(Jack H Hansen )相关推荐

  1. 【JS 逆向百例】Fiddler 插件 Hook 实战,某创帮登录逆向

    关注微信公众号:K哥爬虫,QQ交流群:808574309,持续分享爬虫进阶.JS/安卓逆向等技术干货! 声明 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途 ...

  2. vim插件vundle实战

    vim加装Bundle(vundle)插件管理工具 Vundle(https://github.com/VundleVim/Vundle.vim)的全称是Vim Bundle,它是一款Vim插件管理工 ...

  3. [Vue CLI 3] 插件编写实战和源码分析

    当你看过了官方的几个插件之后,慢慢地,其实你也有需求了. 那如何编写一个 Vue CLI 3 的插件呢? 本文代码已经放到 github 上,地址:https://github.com/dailyno ...

  4. canvas js 绘图插件_[开盖即食]小程序图表插件eCharts实战

    H5时代用来做图表的插件有很多比如:ECharts.Bizcharts.JSCharts等,而这次的小程序本人选用了 ECharts 作为图表组件. 1.选择原因主要有3点: 官方某度在持续维护这个插 ...

  5. Android 插件技术实战总结

    前言 安卓应用开发的大量难题,其实最后都需要插件技术去解决. 现今插件技术的使用非常普遍,比如微信.QQ.淘宝.天猫.空间.携程.大众点评.手机管家等等这些大家在熟悉不过的应用都在使用. 插件技术可以 ...

  6. 【腾讯Bugly干货分享】Android 插件技术实战总结

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/1p5Y0f5XdVXN2EZYT0AM_A 前言 安 ...

  7. 插件制作实战(B站视频评论屏蔽)

    一天,在B站刷着视频,突然想查看up主在视频下的评论,奈何评论太多,很难找到up主的评论,因此便有了做该插件的想法.话不多说,马上开始动手,首先我们应该先了解一下一个插件的构成. 插件框架 在上图中我 ...

  8. 基于Vue的拖拽插件的实战应用,但最后我还是选择了手写

    因为项目上有一个在规定区域内自由拖拽的小需求,自己纯js写又有点小麻烦,就花了点时间寻找到这个小组件. 介绍 vue-drag-resize是一个用于拖拽,缩放的组件 根据网上搜索到的使用教程,都是照 ...

  9. Android组件化与插件化开发项目实战整理分享(含支付宝、360、美团、滴滴等大厂项目实战)

    小公司不说,但是在大公司的项目发展到一定程度,就必须进行模块的拆分.模块化是一种指导理念,其核心思想就是分而治之.降低耦合.而在 Android 开发的实践,目前有两种途径来实现,一个是组件化,一个是 ...

  10. Xposed框架实战

    文章目录 环境 夜神(Android5.1) XposedInstaller(这里我用的是夜神软件商店下载的) 下载后直接点击version进行install 插件(i春秋教程教导) 过程 导入(注意 ...

最新文章

  1. SLF4j+LOG4j
  2. 四、卫星定位《苹果iOS实例编程入门教程》
  3. SAP和CRM相关的标准教材,学通了这些,就算是CRM专家了
  4. mysql binlog rotate_binlog rotate引发的MySQL阻塞事件
  5. 1-3.监督学习(supervised learning)
  6. Java 结构体之 JavaStruct 使用教程一 初识 JavaStruct
  7. Java基础入门笔记-算术运算符
  8. dhcp软件_tp-link无线路由器软件如何升级【教程图解】
  9. python封装概念_Python封装及解构
  10. 【shell编程】1、shell编程简介
  11. 在线可视化python网站_利用Python优雅地可视化数据
  12. 计算机专业毕业设计题目大全
  13. git教程——安装和环境配置(1)
  14. 通过css布局实现去掉window.print()打印界面的页眉页脚
  15. 微信公众号对接网课查题系统
  16. 【学习笔记】Java 开发手册(嵩山版)
  17. MySQL--基础知识点--51--dual
  18. 真正的理解setup time/hold time
  19. 使用spring boot+kubernetes构建完整微服务平台
  20. 【Codecs系列】HEVC-SCC(七):调色板PM模式分析

热门文章

  1. HTTP劫持 方面了解和学习
  2. 转 @PathVariable是什么?详情及用法解析
  3. Tomcat实现Session对象的持久化原理及配置方法介绍
  4. ubuntu如何完全卸载和安装 Java及android环境
  5. 一个 Spring Boot 项目该包含哪些?
  6. Red Hat 发布新 logo:“没有脸了”
  7. 学python编程_程序员学Python编程或许不知的十大提升工具
  8. linux安装postgresql数据库
  9. @SuppressLint(HandlerLeak),或Handler使用有警告;
  10. Maven如何手动添加jar包到本地Maven仓库