SharpDevelop插件系统创建过程全面分析
前言
2005年2月,我申報了一個學校組織的大學生SRTP項目,項目的題目是數據結構動畫演示系統。當初在做項目之前,我無意中買了一本書,書名為《SharpDevelop軟件項目開發全程剖析》。買這本書的目的顯而易見,就是想看看人家老外是怎么做項目的。誰知買來一看,基本看不懂。隨后,我在書里介紹的網站上http://www.icsharpcode.net下載了其項目的源代碼。解壓一看,更是傻掉了,搞了半天還不知道整個程序的主窗體是怎么顯示的,是什么時候顯示的。盡管這樣,我還是沒有氣餒,因為我對它整個程序有很多感興趣的東西:如可拖動的面板,可高亮度顯示的文本,專業的菜單,那些有著良好定義的XML屬性配置文件,等等。所以我就開始認真學習它的源代碼。但是在研究的過程中發現它的工程文件只能用自己才能打開,而且發現SharpDevelop不能調試,使我分析源代碼很不方便。所以,我先從全局分析整個解決方案,最后終于把那些不熟悉項目全部都轉換成了VS中能運行的項目,而且最后整個程序能夠在VS中運行和調試。那是,我為了和大家一起分享我的成果,特地把SharpDevelop的VS版本放在了互聯網上,希望能對那些也想學習其源代碼的朋友有所幫助。從那之后,我就開始研究其插件(Add In)系統?,F在,我已經對其中的三個項目很熟悉,并且將它們完全整合到了我的項目中去,在研究這三個項目的過程中,我對其源代碼文件的目錄結構按照我的理解作了調整,修改和優化了其中的部分源代碼,另外為了幫助理解,我添加了很多的注視。好了,講了很多的廢話,下面我開始分析其源代碼:
1.?一些基本的概念:
AddInTree(插件樹)
SharpDevelop中的所有東西都是被掛接在一棵插件樹中的。這棵插件樹是在程序運行時動態創建的,樹的所有路徑(這些路徑是邏輯路徑)都定義在插件文件中。如果某個插件文件中有以下片斷:
<Extension path = "/Workspace/Services">
??????<Service id????= "FileUtilityService"
?????????????class = "NetFocus.DataStructure.Services.FileUtilityService"/>
??????<Service id????= "MessageService"
?????????????class = "NetFocus.DataStructure.Services.MessageService"/>
??????<Service id????= "TaskService"
?????????????class = "NetFocus.DataStructure.Services.TaskService"/>
</Extension>
<Extension path = "/Workspace/Icons">
??????<Icon id?????????= "C#File"
????????????extensions = ".cs"
????????????resource???= "C#.FileIcon"/>
??????<Icon id?????????= "TextFileIcon"
????????????extensions = ".txt,.doc"
????????????resource???= "Icons.16x16.TextFileIcon"/>
</Extension>
???????則說明插件樹的根節點下有一個子節點Workspace,Workspace下又有Services和Icons兩個子節點,Services下又有FileUtilityService,MessageService,TaskService三個子節點。。。
?
AddIn(插件)
???????SharpDevelop中的一個插件由一個插件文件(以addin為后綴名)定義,該插件文件中定義了很多的插件樹的路徑(由Extension節點指定)。另外,該插件文件中還指定了運行該插件所需的所有的程序集。如果一個插件文件中有一個節點如下:
???????<Runtime>
?????????????<Import assembly="../bin/Base.dll"/>
???????</Runtime>
則說明,運行該插件需要一個名為Base.dll的程序集,該程序集的路徑為一個相對目錄(相對于當前插件文件的目錄)。
Codon(代碼子)
???????按照我的理解,代碼子是一個對象創建器。被創建的對象具有某個具體的功能。如ServiceCodon(服務代碼子,這個名字在SharpDevelop中叫作ClassCodon)通過其BuildItem方法可以創建一個服務對象,該服務對象可為程序提供某種具體的服務;PadCodon(面板代碼子)通過其BuildItem方法可以創建一個面板對象,該面板對象可能是一個屬性面板,文件瀏覽面板,等等。
<Service id????= "FileUtilityService"
?????????????class = "NetFocus.DataStructure.Services.FileUtilityService"/>
<Icon id?????????= "C#File"
????????????extensions = ".cs"
????????????resource???= "C#.FileIcon"/>
上面的兩個XML元素代表兩個代碼子節點,這里我把他們叫做代碼子節點是因為它們在XML文件中以元素(節點)出現,而在程序運行時這兩個元素會被初始化成兩個代碼子對象。像上面這樣,如果某個代碼子節點有Class屬性,則說明該代碼子對象可以根據這個屬性值創建一個對象,如果沒有提供Class屬性,則該代碼子對象直接將自己返回。這里,值得一提的是,所有的Class屬性所指定的類必須在上面介紹的<Runtime>節點中指定的某個程序集中定義過。這一點是我對SharpDevelop源代碼的修改,因為在SharpDevelop中,如果Class定義的某個類在當前插件文件的<Runtime>節點中沒有指定,程序會將當前未初始化好的代碼子對象先保存起來,待當前插件初始化完成之后,再嘗試從后面的插件文件中所指定的程序集中創建該代碼子。
AddInTreeNode(插件樹節點)
???????既然整個程序的結構是一棵插件樹,就肯定有很多插件樹節點。那么插件樹節點有什么功能呢?其實,在整棵插件樹中并不是所有的節點都有功能,有些節點沒有任何功能,只是起到構成路徑的作用;而有些節點如上面所講的FileUtilityService,MessageService,TaskService三個子節點才有具體的功能,因為它們在整棵樹中不僅代表一個節點,而且還代表一個代碼子。實際上,在創建整棵樹的過程中,如果為當前節點指定了一個代碼子對象,則該樹節點就具有具體的功能,如果沒有指定,則該節點只是起到構成路徑的作用。
?
?
?
?
?
?
2.?整個插件系統的創建過程:
為了方便分析,我用我自己的項目代碼來給大家分析,各位看官不必擔心,因為我只是稍微修改了其插件系統,大部分代碼沒有變化。所以在閱讀下面的內容之前建議大家去我的個人網站(http://www.netfocus.cn)下載程序源代碼“數據結構動畫演示系統“,以方便你理解本文。
和SharpDevelop一樣,程序的入口點在/src/StartUp/DataStructureMain.cs的Main函數中。至于前面語句我在這里就不在解釋了,我已經標了注視。下面從AddInTreeSingleton.CreateAddInTree()方法開始分析。首先,從AddInTreeSingleton這個類的名在來看,這個類應該采用Singleton設計模式。下面轉入CreateAddInTree方法,
首先判斷插件樹是否為空,即插件樹是否已經創建,如果沒有創建,則實例化一個默認的插件樹對象。代碼如下:
if(addInTree ==?null)
??????????????{
???????????????????addInTree =?new?DefaultAddInTree();
???????????????????InternalFileService fileUtilityService =?new?InternalFileService();
???????????????????StringCollection addInFiles =?null;
???????????????????if?(ignoreDefaultCoreAddInDirectory ==?false)?//如果沒有忽略默認的插件路徑,即采用默認的插件路徑
???????????????????{
???????????????????????addInFiles = fileUtilityService.SearchDirectory(defaultCoreAddInDirectory, "*.addin");
???????????????????????InsertAddIns(addInFiles);
???????????????????}
???????????????????else??//如果忽略默認的插件文件的路徑
???????????????????{
???????????????????????if?(addInDirectories !=?null)
???????????????????????{
????????????????????????????foreach(string?path?in?addInDirectories)
????????????????????????????{
?????????????????????????????????addInFiles = fileUtilityService.SearchDirectory(Application.StartupPath + Path.DirectorySeparatorChar + path, "*.addin");
?????????????????????????????????InsertAddIns(addInFiles);
????????????????????????????}
???????????????????????}
???????????????????}
??????????????}
在DefaultAddInTree的構造函數中,通過LoadCodonsAndConditions(Assembly.GetExecutingAssembly());
從核心項目Core程序集中加載所有的代碼子和條件對象。那么這個函數到底作了什么呢?
首先從程序集中得到所有的類型,然后判斷如果某個類型是從AbstractCodon抽象類繼承而來并且這個類型上有CodonNameAttribute特性,則說明當前類是一個具有某個功能的代碼子類,然后立即創建一個CodonBuilder對象,并將該對象添加到一個Factory工廠對象的Builders哈希表中。添加時以CodonName為主鍵。另外,對于條件操作也是類似操作。
繼續回到CreateAddInTree方法中,判斷是否忽略默認的插件文件路徑,如果不忽略,則使用默認的插件文件路徑“../addIns“來搜索所有的插件文件,并放到一個字符串集合中。然后調用InsertAddIns方法。下面是這個方法的源代碼:
?????????static?void?InsertAddIns(StringCollection addInFiles)
?????????{
??????????????foreach?(string?addInFile?in?addInFiles)
??????????????{
???????????????????AddIn addIn =?new?AddIn();//先新建一個插件實例
???????????????????try
???????????????????{
???????????????????????addIn.Initialize(addInFile);//通過當前插件文件來初始化這個插件實例
???????????????????????addInTree.InsertAddIn(addIn);//將這個初始化好的插件插入到插件樹中
???????????????????}
???????????????????catch?(Exception e)
???????????????????{
???????????????????????throw?new?AddInInitializeException(addInFile, e);
???????????????????}
??????????????}
?????????????
?????????}
這個方法的功能是接受一個插件文件的集合列表參數,然后對每個插件文件都創建一個插件對象,并將該插件對象插入到插件樹中。在這個過程中,關鍵是插件對象如何創建?以及創建好的對象如何插入到插件樹中?所以,如果這兩個問題解釋清楚了,那么,整個插件系統的創建過程就清楚了?,F面先分析插件對象如何創建,轉入AddIn文件的Initialize方法:
該方法的關鍵是在foreach循環中,它對插件文件根節點下的所有子節點進行迭代,判斷子節點的類型,
如果為RunTime節點,則將該節點的所有子節點所指定的程序集加載到內存,并將該程序集對象添加到一個哈希表中。當然,這里并不是只是簡單的根據程序集文件名創建一個程序集對象,在創建了程序集對象之后,還像剛開始從核心項目Core程序集加載所有的代碼子對象一樣加載了當前程序集中的所有的代碼子和條件對象。
如果為Extension節點,則說明當前節點是一個功能擴展,所謂功能擴展是指在某個插件樹路徑下的功能集合。一般情況下一個功能擴展的特點是:
一個路徑(Path):指定該功能擴展在整個系統中的邏輯路徑;
一些可以嵌套定義的代碼子節點;
一些作用在代碼子節點上的條件節點;
所以,在SharpDevelop中特別定義了一個Extension類,專門用來描述XML文件中定義的Extension節點。
在遇到Extension節點時調用AddExtensions方法,該方法接受一個Extension XML節點,在AddExtensions方法內部關鍵是AddCodonsToExtension方法,該方法比較復雜,下面我分析其中最關鍵的default部分:
?
?
首先通過ICodon codon = AddInTreeSingleton.AddInTree.CodonFactory.CreateCodon(this, curEl);這句話創建一個代碼子對象,大家一定還不清楚這句話是如何工作的。下面,我再詳細解釋:
AddInTree這個對象我在前面已經顯式創建好了,大家應該還記得。然后是CodonFactory的CreateCodon方法。不知大家是否還記得,在先前加載每個程序集的時候,都調用了LoadCodonsAndConditions這個方法,在這個方法內部掃描當前程序集的所有類型,并把繼承自AbstractCodon類的所有子類都封裝在一個對應的CodonBuilder對象中,最后把這些CodonBuilder都添加到了CodonFactory的Builders集合中。所以,當現在調用CodonFactory的CreateCodon方法時,會先根據代碼子名(CodonName)找到一個相應的CodonBuilder對象,然后調用該對象的BuildCodon方法,在該方法中有這么一句話:
codon = (ICodon)assembly.CreateInstance(ClassName,?true);
它通過一個具體的類名從一個程序集中創建一個代碼子對象,最后將該代碼子對象和當前插件對象關聯。
知道了代碼子對象如何創建之后,程序通過以下這句話:
AutoInitializeAttributes(codon, curEl);
上面這個函數的功能是全面檢索當前被創建的代碼子對象的所有的屬性(包括基類的屬性),
如果被掃描到的屬性在XML文件中指定了值,則把該值賦給這個屬性,如果沒有指定,則不賦值。掃描時屬性分為兩類,一般屬性和數組類型的屬性。然后,程序對代碼子進行排序之后將當前代碼子對象插入到擴展對象e的一個集合中。最后,如果當前節點有子節點(如<MenuItem>節點中有子<MenuItem>節點),則對其子節點進行遞歸操作。
好了,下面回到Initialize方法。當該方法按照上面那樣執行結束之后,系統應該做了以下兩件重要事情:1)系統已經加載了該插件運行所需的全部程序集;2)系統已經將這些程序集中的所有的代碼子類和條件類全部創建完成,并都添加到了相應的Extension對象的codonCollection和Conditions集合中。
?
下面開始討論AddInTree的InsertAddIn方法。
第一步,先將傳過來的插件對象加入到一個插件集合中,剛才談到一個Extension對象的codonCollection集合包含了該插件的某些代碼子對象。所以,接下來先在外層循環對該插件對象的Extensions集合列表進行迭代,在該循環內部,先通過Extension對象所指定的Path屬性創建一個插件樹節點,注意創建該節點時總是以root為樹的根節點,因為Path所指定的邏輯插件路徑總是從根節點出發的。如下面的語句所示:
DefaultAddInTreeNode currentNode = CreateTreeNode(root, extension.Path);
接下來,再對Extension里的codonCollection集合進行迭代,并把這些codon對象作為插件樹的節點一個個插入到相應的位置。并將當前插件樹節點與一個代碼子對象關聯,使其具有某種具體的功能。注意:這里插入時,將代碼子對象的ID屬性作為了路徑的一部分,其實就是將一個代碼子對象也作為一個樹節點看待,節點的名字就是該代碼子對象的ID屬性。這里有一個關于插件樹節點如何創建的問題。這個功能是通過以下函數完成的:
DefaultAddInTreeNode CreateTreeNode(DefaultAddInTreeNode currentNode,?string?path)
該函數接受一個當前節點對象和一個樹路徑字符串信息。首先將該路徑字符串進行分割,放在一個數組中(這個數組中的每個元素代表一個樹節點的名稱),然后根據這個數組中的節點名逐個構造樹節點。有兩種情況:
1)??如果由某個節點名指定的節點對象已經創建好了,則直接將該節點作為下一次的當前節點,并進入下一個循環繼續創建其子節點。
2)??如果由某個節點名指定的節點對象還沒有創建,則馬上創建該節點并將其作為當前節點的子節點。
所以從插件樹中節點的創建過程來看,整棵插件樹在邏輯上是一棵樹,但在內部其實是為每個節點都設置了一個ChildNodes集合,用來存放當前節點的子節點。另外對于條件對象也是一樣,這里就不在詳細討論了。當InsertAddIn方法執行完畢時,已經順利地將當前插件對象插入到插件樹中。最后當AddInSingleton的InsertAddIns方法執行完成時,整棵插件樹就應該成功地被創建了。
到這里,各位看官應該已經了解了整棵插件樹是怎樣被創建和組織起來的。下面為了清楚起見,我再簡單回顧一下整個創建流程:
1:在Main函數中顯式調用AddInTreeSingleton的CreateAddInTree()方法
???????AddInTreeSingleton.CreateAddInTree();
2:先獲取所有插件文件列表,然后調用InsertAddIns()方法
???????InsertAddIns(addInFiles);
3:對文件類表進行迭代,對每個插件文件都創建一個插件,并將其插入到插件樹中
???????static?void?InsertAddIns(StringCollection addInFiles)
4:用一個XML插件文件來初始化一個插件對象
???????addIn.Initialize(addInFile);
5:在上面的插件初始化函數中,先分析<RunTime>元素節點,并創建一些CodonBuilder和ConditionBuilder對象,這些對象可以創建Codon和Condition對象。用以下函數實現:
???????AddRuntimeLibraries(curEl);
6:分析<Extension>元素節點,根據這些節點并利用上面所提到的CodonBuilder和ConditionBuilder對象來創建Codon和Condition對象。
???????AddExtensions(curEl);?――>?AddCodonsToExtension
7:插件對象創建好之后,將該插件對象插入到插件樹中。
???????addInTree.InsertAddIn(addIn);
8:在將插件對象插入到樹的過程中,Extension對象的Path屬性只用于構成樹的路徑,而Extension對象的CodonCollection集合中的每一個Codon對象不僅作為一個樹節點(構成路徑),而且具有某個具體的功能。創建樹節點的函數如下:
???????DefaultAddInTreeNode CreateTreeNode(DefaultAddInTreeNode currentNode,?string?path);
9:最后層層返回,最終完成整棵插件樹的創建。
?
?
小結:
整個插件系統的創建應該說比較復雜,其中用到了一些設計模式:
1.因為插件樹在一個程序一次運行中只有一個實例,所以采用了Singleton設計模式;
2.代碼子和條件對象的創建方式,程序中使用了“Builder”字眼,看上去好像是使用了Builder設計模式,但我認為這跟Builder設計模式的真正用途是不一樣的。因為Builder設計模式強調一個對象的創建過程,并最終將該對象返回。而這里只是簡單的創建了一個代碼子或條件對象,真正的創建任務是由代碼子或條件對象來完成的。所以,這樣來看這用方式應該更加符合Proxy設計模式,各位看官覺得如何呢?
3.CodonFactory這個類,一看就是工廠設計模式。但它不是一個一般的工廠模式,而是對一個工廠模式的優化,因為它可以創建任意種類的Codon對象,它并沒有提供一個固定的創建一系列產品的方法列表接口。而是提供了一個集合,集合重的元素可以動態添加,每個元素都可以創建一個Codon或Condition對象。
插件系統如何創建今天就先介紹到這里,明天介紹Command、Service以及插件樹節點中的每個功能是如何來體現和使用的。
總結
以上是生活随笔為你收集整理的SharpDevelop插件系统创建过程全面分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深入Redis内部-Redis 源码讲解
- 下一篇: wince 6.0 设备管理器架构