三、插件系统
上回书说到SharpDevelop入口Main函数的结构,ServiceManager.Service在InitializeServicesSubsystem方法中首次调用了AddInTreeSingleton的AddInTree实例,AddInTree在这里进行了初始化。本回进入AddInTree着重讲述SharpDevelop的插件系统。在叙述的时候为了方便起见,对于“插件”和插件具体的“功能模块”这两个词不会特别的区分,各位看官可以从上下文分辨具体的含义(而事实上,SharpDevelop中的“插件”是指.addin配置文件,每一个“插件”都可能会包含多个“功能模块”)。
1、插件的配置
既然说到插件系统,那么我们先来看一看SharpDevelop插件系统的组织形式。
很多时候,同一个事物从不同的角度来看会得出不一样的结论,SharpDevelop的插件系统也是如此。在看SharpDevelop的代码以前,按照我对插件的理解,我认为所谓的“插件”就是代表一个功能模块,插件的配置就是描述该插件并指定如何把这个插件挂到系统中。SharpDevelop中有插件树的思想,也就是每一个插件在系统中都有一个扩展点的路径。那么按照我最初对插件的理解,编写插件需要做的就是:
A、根据插件接口编写功能模块实现一个Command类
B、编写一个配置文件,指定Command类的扩展点(Extension)路径,挂到插件树中
之后按照这样的理解,我编写了一个察看插件树的插件AddinTreeView,打算挂到SharpDevelop中去。根据SharpDevelop对插件的定义,我把具体插件的AddinTreeViewCommand实现了之后,编写了一个配置文件AddinTreeView.addin如下:
在配置文件中,Runtime节指定了插件功能模块所在的库文件Addins.dll的具体路径,在Extension节中指定了扩展点路径/SharpDevelop/Workbench/MainMenu/Tools(我是打算把它挂到主菜单的工具菜单下),然后在Extension内指定了它的Codon为 MenuItem以及具体的ID、标签、Command类名。这样做,SharpDevelop运行的很不错,我的插件出现在了Tools菜单下。之后,我又编写了一个SharpDevelop的资源管理器(ResourceEditor)的插件类ResourceEditor.dll并把它挂到Tool菜单下。同样的,我也写了一个ResourceEditor.addin文件来对应。系统工作的很正常。
如果我们对于每一个插件都编写这样的一个配置文件,那么插件的库文件(.dll)、插件配置文件(.addin)是一一对应的。不过这样就带来了一个小小的问题,在这样的一个以插件为基础的系统中,每一个菜单、工具栏按钮、窗体、面板都是一个插件,那么我们需要为每一个插件编写配置文件,这样就会有很多个配置文件(似乎有点太多了,不是很好管理)。SharpDevelop也想到了这个问题,于是它允许我们把多个插件的配置合并在一个插件的配置文件中。因此,我把我的两个插件库文件合并到一个Addins工程内生成了Addins.dll,又重新编写了我的插件配置文件MyAddins.addin如下:
这样,我把两个插件的功能模块使用一个插件配置文件来进行配置。同样的,我也可以把几十个功能模块合并到一个插件配置文件中。SharpDevelop把这个插件配置文件称为“Addin(插件)”,而把具体的功能模块封装为Codon,使用Command类来包装具体的功能。SharpDevelop本身的核心配置SharpDevelopCore.addin里面就包含了所有的基本菜单、工具栏、PAD的插件配置。
我们回过头来看一下,现在我们有了两颗树。首先,插件树本身是一个树形的结构,这个树是根据系统所有插件的各个Codon的扩展点路径构造的,表示了各个Codon在插件树中的位置,各位看官可以通过我写的这个小小的AddinTreeView来看看SharpDevelop中实际的结构。其次,插件的配置文件本身也具有了一个树形的结构,这个树结构的根节点是系统的各个插件配置文件,其下是根据这个配置文件中的Extension节点的来构成的,描述了每个Extension节点下具有的Codon。我们可以通过SharpDevelop的Tools菜单下的AddinScout来看看这个树的结构。
我为了试验,把SharpDevelop的插件精简了很多,构成了一个简单的小插件系统。下面是这个精简系统的两个树的截图。各位看官可以通过这两副图理解一下插件树和插件配置文件的关系(只是看同样问题的两个角度,一个是Codon的ExtensionPath,一个是配置文件的内容)。
总结一下SharpDevelop插件的配置文件格式。首先是
节点,需要指定AddIn的名称、作者之类的属性。其次,在AddIn节点下的节点内,使用来指定本插件配置中Codon所在的库文件。如果分布在多个库文件中,可以一一指明。然后,编写具体功能模块的配置。每个功能模块的配置都以扩展点开始,指定了路径(Path)属性之后,在这个节点内配置在这个扩展点下具体的Codon。每个Codon根据具体不同的实现有不同的属性。各位看官可以研究一下SharpDevelop的核心配置文件SharpDevelopCore.addin的写法,相信很容易理解的。
2、插件系统的核心AddIn和AddInTree
前文讲到,在SharpDevelop的Main函数中,ServiceManager.Service在InitializeServicesSubsystem方法中首次调用了AddInTreeSingleton的AddInTree实例,AddinTree在这个时候进行了初始化。现在我们就来看看AddInTreeSingleton.AddInTree到底做了些什么事情,它定义在\src\Main\Core\AddIns\AddInTreeSingleton.cs文件中。
public static IAddInTree AddInTree
{
get
{
if (addInTree == null)
{
CreateAddInTree();
}
return addInTree;
}
}
AddInTreeSingleton是插件树的一个Singleton(具体的可以去看《设计模式》了),AddInTreeSingleton.AddInTree是一个属性,返回一个IAddinTree接口。这里我注意到一点,AddInTreeSingleton是从DefaultAddInTree继承下来的。既然它是一个单件模式,包含的方法全部都是静态方法,没有实例化的必要,而且外部是通过AddInTree属性来访问插件树,为什么要从DefaultAddInTree继承呢?这好像没有什么必要。这也许是重构过程中被遗漏的一个小问题吧。
我们先来看看IAddinTree接口的内容,它定义了这样的几个内容:
A、属性ConditionFactory ConditionFactory 返回一个构造条件的工厂类,这里的条件是指插件配置中的条件,我们以后再详细说明。
B、属性CodonFactory CodonFactory 返回一个构造Codon的工厂类。
C、属性AddInCollection AddIns 返回插件树的根节点Addin(插件)集合。
D、方法IAddInTreeNode GetTreeNode(string path) 根据扩展点路径(path)返回对应的树节点
E、方法void InsertAddIn(AddIn addIn) 根据AddIn中的扩展点路径添加一个插件到树中
F、方法void RemoveAddIn(AddIn addIn) 删除一个插件
G、方法Assembly LoadAssembly(string assemblyFile) 读入插件中Runtime节的Import指定的Assembly,并构造相应的CodonFactory和CodonFactory类。
AddInTreeSingleton在首次调用AddInTree的时候会调用CreateAddInTree方法来进行初始化。CreateAddInTree方法是这样实现的:
addInTree = new DefaultAddInTree();
初始化插件树为DefaultAddInTree的实例,这里我感受到了一点重构的痕迹。首先,DefaultAddInTree从名称上看是默认的插件树(既然是默认,那么换句话说还可以有其他的插件树)。但是SharpDevelop并没有给外部提供使用自定义插件树的接口(除非我们修改这里的代码),也就是说这个名称并不像它本身所暗示的那样。其次,按照Singleton通常的写法以及前面提到AddInTreeSingleton是从DefaultAddInTree继承下来的疑问,我猜想DefaultAddinTree的内容本来是在AddinTreeSingleton里面实现的,后来也许为了代码的条理性,把实现IAddinTree内容的代码剥离了出去,形成了DefaultAddinTree类。至于继承DefaultAddInTree的问题,也许这里本来是一个AddInTree的基类。这是题外话,也未加证实,各位看官可以不必放在心上(有兴趣的可以去找找以前SharpDevelop的老版本的代码来看看)。
这里有两个察看代码的线路,一个是DefaultAddInTree的构造函数的代码,在这个构造函数中构造了Codon和Condtion的工厂类。另外一个是CreateAddInTree后面的代码,搜索插件文件,并根据插件文件进行AddIn的构造。各位看官可以选择走分支线路,也可以选择先看主线(不过这样你会漏掉不少内容)。
2.1 支线 (DefaultAddInTree的构造函数)
我们把CreateAddInTree的代码中断一下压栈先,跳到DefaultAddInTree的构造函数中去看一看。DefaultAddInTree定义在\src\Main\Core\AddIns\DefaultAddInTree.cs文件中。在DefaultAddInTree的构造函数中,注意到它具有一个修饰符internal,也就是说这个类只允许Core这个程序集中的类对DefaultAddInTree进行实例化(真狠啊)。构造函数中的代码只有一句:
LoadCodonsAndConditions(Assembly.GetExecutingAssembly());
虽然只有一行代码,不过这里所包含的内容却很精巧,是全局的关键,要讲清楚我可有得写了。首先,通过全局的Assembly对象取得入口程序的Assembly,传入LoadCodonsAndConditions方法中。在该方法中,枚举传入的Assembly中的所有数据类型。如果不是抽象的,并且是AbstractCodon的子类,并且具有对应的CodonNameAttribute属性信息,那么就根据这个类的名称建立一个对应的CodonBuilder并它加入CodonFactory中(之后对Condition也进行了同样的操作,我们专注来看Codon部分,Condition跟Codon基本上是一样的)。
这里的CodonFactory类和CodonBuilder类构成了SharpDevelop插件系统灵活的基础,各位看官可要看仔细了。
我们以实例来演示,以前文我编写的AddinTreeViewCommand为例。在入口的Assembly中会搜索到MenuItemCodon,它是AbstractCodon的一个子类、包装MenuItem(菜单项)Command(命令)的Codon。符合条件,执行
codonFactory.AddCodonBuilder(new CodonBuilder(type.FullName, assembly));
首先根据类名MenuItemCodon和assembly 构造CodonBuilder。CodonBuilder定义在\src\Main\Core\AddIns\Codons\CodonBuilder.cs文件中。在CodonBuilder的构造函数中根据MenuItemCodon的CodonNameAttribute属性信息取得该Codon的名称MenuItem。CodonNameAttribute描述了Codon的名称,这个MenuItem也就是在.addin配置文件中对应的
0 条评论:
发表评论
订阅 博文评论 [Atom]
<< 主页