0、简介
SharpDevelop系统是一个功能比较齐全的类似于Visual Studio的开源代码编辑工具,它已经有超过10年的开发历史。它没有Visual Studio功能那么多,但基本的代码编辑、编译、调试,代码自动完成等编辑功能都比较齐全。SharpDevelop系统有很多值得学习的地方,比如插件管理、代码编辑、语法分析、重构等功能模块。
我一直都对SharpDevelop系统感兴趣,想学习里面的软件结构及功能模块。但是每次看到它那几十万行代码都觉得头大,不知道该从何看起以及什么时候能看完,结果每次看过一两个文件之后就放弃了。今年,我又重新鼓起勇气,看一部分就写一部分的心得体会,争取能多学一点东西。
SharpDevelop系统是由C#语言编写的,本文及后面关于SharpDevelop系统的文章参考的版本是5.1.0.5216。文章中有些内容是猜测的,因为代码是一部分一部分来看的,有些功能或者类型不清楚是什么意思或者起什么作用,这些会随着进一步阅读代码在后续的文章中纠正。
1、插件系统介绍
为了支持功能扩展,SharpDevelop系统有一套完整的插件管理方法,开发者只要遵循插件编辑规范,就可以将需要的功能以插件的形式放到SharpDevelop系统中。
有多种方式可以实现插件系统。1)通用接口。定义一个通用的接口,所有的插件都必须实现该接口,然后主程序以某种方式(可以将所有的插件都放在一个文件夹内,然后加载文件夹内的所有程序集,也可以将插件信息放到一个配置文件中,根据配置文件的信息加载插件)加载这些插件;2)不定义通用接口,仅将插件以某种方式加载到主程序中,然后使用插件中的功能。这种方式比第一种方式要复杂,因为主程序不知道插件中有哪些功能可以调用,这要求主程序必须能够从插件中获取需要的信息,然后正确调用插件提供的功能。
SharpDevelop系统是以第二种插件实现方式来管理插件的。整个SharpDevelop系统存在一个唯一的插件树,如下图所示。SharpDevelop系统中的所有主要功能都对应着插件树上的一条路径。比如SharpDevelop\Services路径下面存放着系统里面的所有服务,如果插件中也有服务,则将插件中的服务也放到该路径下,又如SharpDevelop\Workbench\MainMenu下面放的是主程序的主菜单下的菜单项,插件如果需要在主菜单下面创建菜单,必须在该路径下放置相应的菜单项。
SharpDevelop系统中插件树上的有些路径是固定写死的。例如/Workspace/Icons、/SharpDevelop/Services。系统在启动及执行过程中,会在插件树的不同路径中取相应的功能进行执行。例如当系统启动时,会在插件树的路径/SharpDevelop/Autostart下取所有的对象,然后依次执行,因为这个路径下的对象都是需要系统启动时需要自动运行的,类似于操作系统中注册表HKEY_LOCAL_MACHINE\ SOFTWARE\Microsoft\Windows\CurrentVersion\Run下的那些需要自动运行的程序(可以将插件树与注册表进行类比)。
插件不等于插件树。插件为插件树提供路径和路径下的功能。SharpDevelop系统根据插件中提供的路径构造插件树,然后将插件中的功能放在插件树指定的路径下。SharpDevelop系统启动时,根据本身配置的插件路径、插件列表以及从命令行中传入的插件路径,加载所有的插件,设置插件的安装、启用、停用、卸载状态,读取每个插件的配置文件(.addin文件),从其中获取插件的依赖项、可以提供的类型、图形和字符串资源已经插件树上的路径信息,然后将这些信息放到插件树或者响应的服务中进行统一管理,程序启动时根据需要调用这些内容。
2、插件
SharpDevelop系统可以视为一个插件管理系统,它其中的主要功能都是通过插件的方式实现的。插件包含两部分关键内容:1)插件的相关程序集,包含插件的功能模块及相关数据类型;2)插件的配置文件,在配置文件中详细描述了插件能够为SharpDevelop系统提供哪些类型及哪些功能模块。SharpDevelop系统并不主动加载和使用插件程序集中的所有公开类型,而是根据配置文件中的描述选择性的使用插件程序集中的所有公开类型。
插件配置文件的文件后缀名是addin,它与插件相关程序集放在同一个目录下。插件配置文件的结构如下图所示。
![SharpDevelop源码分析之插件 - gc_2299 - gc_2299的博客 SharpDevelop源码分析之插件 - gc_2299 - gc_2299的博客](http://img2.ph.126.net/trivAE4E_XjT64TcEFBrFA==/6631685691235169882.png)
2.1、基本属性
主要描述插件的基本信息。包括插件的名称、插件的作者、插件的简单描述,是否需要预先加载插件等。这些内容放在配置文件的根节点<AddIn>的属性中。
2.2、Manifest
主要描述该插件加载时需要关注的信息。其中1)Identity标签中包含插件的标识符(也就是插件的唯一ID)和版本信息,Identity可以有多个,代表的都是这个插件;2)Dependency标签中包含该插件的依赖插件及依赖插件的版本信息,也就是说必须首先加载它依赖的指定版本的插件才能加载该插件;3)Conflict标签中包含与该插件存在冲突的插件及版本信息,如果SharpDevelop系统中已经加载了与该插件存在冲突的指定版本的插件,则不能加载该插件。
2.3、Runtime
Runtime标签中:
1)子标签Import包含插件相关的程序集以及可以从程序集中导出的IConditionEvaluator和IDoozer类型信息。Import标签的addembly属性是指需要导入的程序集名称,下面的子标签是需要从该程序集中导入的类的标识和类名。一个Import标签对应一个程序集,一个Import标签对应一个runtime对象。ConditionEvaluator标签中指定的类用于评估指定的条件是否满足要求,该类必须继承自接口IConditionEvaluator。该接口中仅提供有一个函数,用于确定传入的参数是否满足条件,具体实现时可以用到这两个参数,也可以不用。Doozer标签中的类型用于创建一类对象或者实现一种功能。
2)Condition和ComplexCondition标签表示加载程序集需要满足哪些条件,具体条件与程序集之间的关系可以参考4.3节。
3)DisableAddIn暂时不知道用途。
2.4、Path
Path标签表示插件在Sharpdevelop系统的插件树中的一条路径,插件中可以提供多个不同的Path,每个Path包括一个路径及向该路径中注册内容(Codon),如果该内容不是随时都可用,每个Codon还对应一个或者多个条件组合,供SharpDevelop系统判断是否可用使用该内容。
![SharpDevelop源码分析之插件 - gc_2299 - gc_2299的博客 SharpDevelop源码分析之插件 - gc_2299 - gc_2299的博客](http://img0.ph.126.net/UFUUwM9MqH7OixAZk3bUMQ==/6631508669863099032.png)
Path标签中的Name属性指明当前插件在插件树中的位置。每个Path对应一个ExtensionPath对象。Path下面的子标签,除了名为Condition和ComplexCondition的子标签外,其它每个子标签都对应一个Codom。
一个Codon子标签,可以被一个或者多个Condition或者ComplexCondition标签包含。这样说明Codon必须满足这些条件才能执行。以上面截图为例。名为Debug的menu Codom下面包含两个子菜单Run和RunWithoutDebugger,这两个子菜单分别被三个Condition标签包含,说明如果执行Run或者RunWithoutDebugger,必须要满足DebuggerSupports、SolutionOpen和IsProcessRuning三个条件才行。每个Condition都在Runtime标签中的ConditionEvaluator列表中定义过了,这里只需要设置好Condition对应的名称即可。Condition和Codon和关系请参考4.3节。
2.5、Include
这个标签代表的内容不是很明白,因为在SharpDevelop系统下的目前所有的addin文件中很少见到这个标签(IncludeDoozer不算)。通过阅读代码,认为是一个插件除了主配置文件之外,可能还存在多个辅助配置文件,这些辅助配置文件中的内容与主配置文件相同,只是不再包含插件的基本信息,根目录下直接就是2.2-2.6节的标签。SharpDevelop系统将主配置文件和辅助配置文件中的信息组合到一起,共同组成该插件的信息。Include标签中包含的唯一属性内容就是辅助配置文件的路径信息
2.6、StringResources和BitmapResources
插件中包含其需要使用的图片资源、字符串资源文件路径,分别包含在BitmapResources和StringResources标签中。这些路径在构建插件树的过程中,由SharpDevelop系统加入到资源服务类中统一进行管理。
3、Codon和IDoozer
在Path标签下,除了Condition和ComplexCondition以外的每一个子标签都可以视为一个Codon。一个Codon标签对应着一个对象或者一种功能实现,它是插件树上每个路径下的基本单元。Codon类是一个通用类,它的name属性对应的Path标签下的Codon标签名称,相当于对象类型。Codon描述的每一种对象或者功能都有一个具体的IDoozer类型创建或者实现该对象或者功能。附表1中列举一些常用的Codon及其对应的IDoozer类。
4、IConditionEvaluator和ICondition
4.1、IConditionEvaluator和ICondition的关系
IConditionEvaluator接口用于评估SharpDevelop系统中的某一类状态是否满足指定的条件,继承自IConditonEvaluator接口的类各自表示SharpDevelop系统中的一类状况,例如SolutionOpenConditionEvaluator用于判断SharpDevelop系统是否打开了一个解决方案。
ICondition接口定义布尔逻辑上的条件,它有两种子类:复杂条件和简单条件。复杂条件中包含布尔逻辑中的三种操作与(AndCondition)、或(OrCondtion)、非(NegatedCondition),这三种操作中既可以包含简单条件,也可以再嵌套复杂条件。简单条件中只有一个通用的条件描述类Condition,它本身并不进行条件判断,而是包含一个条件类型名称,该名称对应IConditionEvaluator接口的一个子类,并且Condtion类中指明需要满足哪些条件才认为条件为真。
ICondition与IConditionEvaluator的关系如下图所示:
![]() |
4.2、ComplexCondition结构
通过查阅代码,发现配置文件中的ComplexCondition结构有以下要求:
1)ComplexCondition标签后面必须紧跟至少一个And、Or或Not标签,然后才能加Codon或者Condition。
2)And、Or和Not这三种标签中应该只嵌套条件或者循环嵌套这三种条件,不能再其中放置Codon或者ComplexCondition。
3)ComplexCondition标签下的第一级子标签中的所有条件(简单和复杂)都是与的关系。
4.3、ICondition与Codon的关系实例描述
一个Codon对应的内容或者功能是否可用是有ICondition代表的条件来决定的。有以下几种情况:
1)什么时候都可以用,这时不需要指定条件,在Path标签下直接见Codon子标签即可,如下图所示。
![SharpDevelop源码分析之插件 - gc_2299 - gc_2299的博客 SharpDevelop源码分析之插件 - gc_2299 - gc_2299的博客](http://img1.ph.126.net/x7t-MXWFhVFmOP8ekFlqGQ==/6631761557537484409.png)
2)只有满足一个或者多个简单条件时才能使用该Codon。这时在Path标签下首先建立Condition子标签,然后在Condition子标签中再嵌套Codon标签。看起来Condition子标签在Codon标签外面,环绕着Codon,这也很形象的表示Codon需要满足Condition才能使用。如果是多个Condition的话,将这些Condition依次嵌套,然后把Codon子标签放在最里面,看起来像多个Condition一层层的包围Codon,必须满足这些Condition才能使用Codon。如下图所示。
![SharpDevelop源码分析之插件 - gc_2299 - gc_2299的博客 SharpDevelop源码分析之插件 - gc_2299 - gc_2299的博客](http://img2.ph.126.net/L2LdKvHaOKULTJIlBx9DOg==/6631704382932839948.png)
3)需要满足复杂条件和简单条件的组合才能使用该Codon。这种情况下,简单条件和复杂条件相同嵌套。必须要同时满足这些条件才能使用该Codon。
上图是从SharpDevelop系统中文件ICSharpCode.SharpDevelop.addin截取的一段内容。可以发现第一级是一个复杂条件,第二级中包含有Not、Condition、ComplexConditon等条件,第三级中包含Or、ComplexCondition、Condtion等条件,第四级中包含有Or、Condition等条件。这应该是一个比较复杂的情况了。其中id为OpenSeparator的MenuItem需要满足Not和Condition两类条件才能使用,而id为ExcludeFile的MenuItem需要满足Not、Or、Or和Condition四类条件才能使用。
那如何确定一个Codon包含哪些条件呢?可以采用瞻前不顾后的方法,找到Codon所在的Path标签,在Path标签与该Codon之间做一个连接线。在这个连接线范围内,所有未封闭的Condition标签,未封闭的ComplexCondition标签中包含的条件就是与该Codon关联的条件。比如下图中id为RunWithoutDebugger的Codon的条件包含Or、名为IsProcessRunning的条件、名为SolutionOpen的条件、名为DebuggerSupports的条件。而id为DebuggerSeparator的Codon就没什么关联条件,因为它前面就只有一个Path标签。
![]() |
4.4、简单条件和复杂条件之间的转换
简单条件和复杂条件之间可以做有限的转换。比如名为MenuItem的Codom需要满足三个条件才能使用。
如果用简单条件表述的话如下:
<Condition>
<Condition>
<Condition>
<MenuItem …/>
</Condition>
</Condition>
</Condition>
如果用负责条件表述的话如下:
<ComplexCondition>
<And>
<Condition …/>
<Condition …/>
<Condition …/>
</And>
<MenuItem …/>
</ComplexCondition>
目前只主要看了ICSharpCode.Core这些项目,在其中做了一些注释,下面的链接中就是包含我写的注释的的项目代码,有兴趣的可以看看:http://pan.baidu.com/s/1bo0eGR9
附件是到目前为止看到的Codon,ConditionEvaluator和Service,读代码的时候顺便进行整理汇总。
附表1、SharpDevelop中的Codon及其对应的IDoozer
![SharpDevelop源码分析之插件 - gc_2299 - gc_2299的博客 SharpDevelop源码分析之插件 - gc_2299 - gc_2299的博客](http://img1.ph.126.net/W3a6Zoqt1zAPaAo5PECOMA==/6631510868886354823.png)
附表2、SharpDevelop中的IConditonEvaluato
![SharpDevelop源码分析之插件 - gc_2299 - gc_2299的博客 SharpDevelop源码分析之插件 - gc_2299 - gc_2299的博客](http://img2.ph.126.net/uDAejz3hFlQXrNsJILxJDw==/6631754960467724882.png)
附表3、SharpDevelop中的Service