在大学课程里面,我对于模拟电路总是搞不清楚,直到现在也是这样。我总觉得电路图很奇怪,总会问“这部分电路是做什么用的”、“为什么会有这样的效果”。在我的脑海里面,每部分的电路都应该有一定的用处,可是我总是看不明白。我妈妈说,我的思路被软件所固化的太久了,看电路图不应该总是一个个模块的看,正确的方法应该是从电源的一极顺着电路看,一直看到电源的另一极。我现在仍然不懂看电路图,可是以我看代码的经验来说,我觉得分析源代码按照这样的思路来看会比较容易把脉络理清楚。
在sharpdevelop的代码中,由于很多的接口和插件的原因,很多代码在看到某个地方会突然失去函数/方法调用的线索。例如看某个函数的实现的时候会跳到一个接口里面去,那是因为这部分功能在运行期才会给一个实现了这个接口的对象来进行具体的执行。从这个角度来说,设计模式也给我们研究代码稍微带来了一点小小的难度。在看linux下源代码的时候也经常遇到这种问题,在这个时候寻找代码线索比较好的方法是用一个文本搜索工具来搜索相关的关键字。在linux下我经常会用grep,windows下面类似ultraedit的“批量文件查找”功能会很好用(或者“search and replace”之类的工具)。这个是我读代码的一点小小的经验,如果你知道有更好的方法,请告诉我让我也学习一下 ? 。
我不想大段大段的贴代码出来占地方(空间、带宽,还有各位看官的注意力),在需要的地方我会贴上主要的代码,因此最好能够找代码来对应着看。把代码包解压缩,我把它解到了“f:\sharpdevelop”(如果没有说明,下文都是以此为代码的根目录了)。由于sharpdevelop本身对于察看代码不是很方便,没有“转到定义”之类的功能,因此我建议你把它的代码转成vs的工程来看。不过很可惜,sharpdevelop的工程导出功能现在有问题,如果导出\src\sharpdevelop.cmbx 这个总的复合工程的话会失败(我记得rc1版本是可以成功的,不知道为什么后来的版本反而会出问题),所以只能一个一个工程的导出。
好了,让我们来看sharpdevelop的代码吧。
1、小技巧的起点
在主程序的起点在\src\main\startup\sharpdevelopmain.cs,找到main函数这就是整个程序的起点了。开始的部分是显示封面窗体并加上命令行控制,其中splashscreenform 定义在\src\main\base\gui\dialogs\splashscreen.cs文件中,这部分我就不多说了。之后是
application.threadexception += new threadexceptioneventhandler(showerrorbox);
sharpdevelop为了有效的进行错误报告,因此自己进行了异常的控制。系统出现异常的时候,sharpdevelop会拦截下来弹出它自己的异常提示报告对话框。这个代码就是在这一行实现的。其中 showerrorbox 这个方法就在类sharpdevelopmain中,exceptionbox 定义在\src\main\startup\dialogs\exceptionbox.cs中。如果需要进行自己的异常控制,可以学习一下这里的技巧。
2、充满玄机的初始化
string [] addindirs = icsharpcode.sharpdevelop.addinsettingshandler.getaddindirectories( out ignoredefaultpath );
addintreesingleton.setaddindirectories(addindirs, ignoredefaultpath);
通过addinsettingshandler取得插件的目录,并告知addintreesingleton。addinsettingshandler定义在\src\main\startup\dialogs\addintreesettingshandler.cs中,它通过读取系统配置(app.config)文件中的addindirectory节点的path属性来确定插件的目录位置,或者你也可以通过自己定义的addindirectories节来指定插件目录。如果你没有做这些配置,默认的目录在sharpdevelop运行目录的..\addins目录下。
servicemanager.services.addservice(new messageservice());
servicemanager.services.addservice(new resourceservice());
servicemanager.services.addservice(new iconservice());
通过servicemanager(服务管理器)加入三个系统默认的服务,消息服务、资源服务、图标服务。这三个服务中,消息服务是显示各种信息提示,另外两个是属于系统的资源,sharpdevelop通过服务来进行统一调用和管理。
servicemanager.services.initializeservicessubsystem("/workspace/services");
初始化其他的服务。sharpdevelop把服务定义在插件树的/workspace/services这个路径中,凡是在这个路径下的插件都被认为是服务,因此如果你自己定义了一个服务的话,也需要挂到这个路径下(这里就是系统服务的扩展点了)。
注意!这一步中,在我们的眼皮子底下悄悄的进行了一个重要的初始化工作。各位看官请看,servicemanager 定义在\src\main\core\services\ servicemanager.cs文件中,察看它的initializeservicessubsystem方法,我们发现这样一行
addservices((iservice[])addintreesingleton.addintree.gettreenode(servicespath).buildchilditems(this).toarray(typeof(iservice)));
在这里,addintreesingleton首次调用了addintree(插件树)的实例。按照singleton模式,只有在首次调用的时候才会初始化实例,这里也是同样如此。整个系统的addintree是在这一步中进行了初始化工作,稍候我们将详细介绍addintree如何进行初始化工作,先顺便看看服务的初始化。在servicemanager的initializeservicessubsystem方法中,通过addintree检索服务插件路径下的所有配置,并通过它来读取、建立具体的对象,然后加入到服务列表中。之后通过一个循环,逐个的调用各个服务的initializeservice方法初始化服务。
addintree的初始化工作容我们稍候再看,先把主体的代码看完。
commands = addintreesingleton.addintree.gettreenode("/workspace/autostart").buildchilditems(null);
for (int i = 0; i < commands.count – 1; ++i) {
((icommand)commands[i]).run();
}
/workspace/autostart是系统自动运行命令的扩展点路径,定义在这个路径下的插件会在系统启动的时候自动运行。在这里,通过插件树初始化建立处于这个路径下的command(命令),并逐一执行。buildchilditems方法的功能是建立这个扩展点下的command列表,我会在介绍addtree的时候具体说明它的实现。
主程序代码的最后,初始化完毕、关闭封面窗体,然后执行命令列表中最后一个命令(也就是系统的主界面)。在主界面退出的时候,系统卸载所有的服务。
在这部分代码中,我们知道了两个系统指定的扩展点路径 /workspace/services 和 /workspace/autostart ,我们实现服务和指定系统自动运行命令的时候就可以挂到这两个扩展点路径下了。
托反射的福,servicemanager.services可以通过类型(接口)来查找具体的实例,也就是getservices方法。但是servicemanager的具体实现我们可以容后再看,这里已经不是最紧要的部分了。
接下来,我们来看看整个插件系统的核心-addintree的代码,看看它是如何通过插件配置进行初始化并建立起整个系统的插件树骨干。