程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> .NET實例教程 >> SharpDevelop源碼分析 (三、插件系統)

SharpDevelop源碼分析 (三、插件系統)

編輯:.NET實例教程

三、插件系統

   上回書說到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如下:



<AddIn name        = "AddinTreeVIEw"
       author      = "SimonLiu"
       copyright   = "GPL"
       url         = "http://www.icsharpcode.Net"
       description = "Display AddinTree"
       version     = "1.0.0">

 <Runtime>
  <Import assembly="../../bin/ AddinTreeVIEw.dll"/>
 </Runtime>

 <Extension path = "/SharpDevelop/Workbench/MainMenu/Tools">
  <MenuItem id = "AddinTreeVIEw" 
   label = "VIEw AddinTree" 
   class = "Addins.AddinTreeView.AddinTreeVIEwCommand"/>
 </Extension> 
</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如下:



<AddIn name        = "MyAddins"
       author      = "SimonLiu"
       copyright   = "GPL"
       url         = "http://www.icsharpcode.Net"
       description = "Display AddinTree"

 version     = "1.0.0">

 <Runtime>
  <Import assembly="../../bin/Addins.dll"/>
 </Runtime>

 <Extension path = "/SharpDevelop/Workbench/MainMenu/Tools">
  <MenuItem id = "ResourceEditor" 
   label = "Resource Editor" 
   class = "Addins.ResourceEditor.Command.ResourceEditorCommand"/> 
  <MenuItem id = "AddinTreeVIEw" 
   label = "VIEw AddinTree" 
   class = "Addins.AddinTreeView.AddinTreeVIEwCommand"/> 
 </Extension> 
</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的名稱、作者之類的屬性。其次,在AddIn節點下的<Runtime>節點內,使用<Import …>來指定本插件配置中Codon所在的庫文件。如果分布在多個庫文件中,可以一一指明。然後,編寫具體功能模塊的配置。每個功能模塊的配置都以擴展點<Extension>開始,指定了路徑(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類。

 &nbsp; 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配置文件中對應的<MenuItem>標簽,後文會看到它的重要用途。在CodonBuilder中除了包含了該Codon的ClassName(類名)和CodonName屬性之外,就只有一個方法BuildCodon了。



  public ICodon BuildCodon(AddIn addIn)
  {
   ICodon codon;
   try {
    // create instance (ignore case)
    codon = (ICodon)assembly.CreateInstance(ClassName, true);
    
    // set default values
    codon.AddIn = addIn;
   } catch (Exception) {
    codon = null;
   }
   return codon;
  }


   很明顯,BuildCodon根據構造函數中傳入的assembly和類型的ClassName,建立了具體的Codon的實例,並和具體的AddIn關聯起來。
   之後,codonFactory調用AddCodonBuilder方法把這個CodonBuilder加入它的Builder集合中。我們向上一層,看看codonFactory如何使用這個CodonBuilder。
   在文件\src\Main\Core\AddIns\Codons\CodonFactory.cs中,codonFactory只有兩個方法。AddCodonBuilder方法把CodonBuilder加入一個以CodonName為索引的Hashtable中。另外一個方法很重要:



  public ICodon CreateCodon(AddIn addIn, XMLNode codonNode)
  {
   CodonBuilder builder = codonHashtable[codonNode.Name] as CodonBuilder;
   
   if (builder != null) {
    return builder.BuildCodon(addIn);
   }
   
   throw new CodonNotFoundException(String.Format("no codon builder found for <{0}>", codonNode.Name));
  }

   在這裡,addin是這個配置文件的描述(也就是插件),而這個XMLNode類型的CodonNode是什麼東西?
   還記得配置文件中在<Extension>標簽下的<Class>、<MenuItem>、<Pad>之類的標簽嗎?我曾經說過,這些就是Codon的描述,現在我們來看看到底是不是如此。以前文的AddinTreeVIEw配置為例:



 <Extension path = "/SharpDevelop/Workbench/MainMenu/Tools">
  <MenuItem id = "AddinTreeVIEw" 
   label = "VIEw AddinTree" 
   class = "Addins.AddinTreeView.AddinTreeVIEwCommand"/> 
 </Extension> 

   SharpDevelop在讀入插件配置文件的<Extension>標簽之後,就把它的ChildNodes(XMLElement的屬性)依次傳入CodonFactory的CreateCodon方法中。


這裡它的ChildNodes[0]就是這裡的<MenuItem id = ..... />節點,也就是codonNode參數了。這個XML節點的Name是MenuItem,因此CreateCodon的第一行



CodonBuilder builder = codonHashtable[codonNode.Name] as CodonBuilder;

   根據節點的名稱(MenuItem)查找對應的CodonBuilder。記得前面的CodonBuilder根據CodonNameAttribute取得了MenuItemCodon的CodonName嗎?就是這個MenuItem了。CodonFactory找到了對應的MenuItemCodon的CodonBuilder(這個是在DefaultAddInTree的構造函數中調用LoadCodonsAndConditions方法建立並加入CodonFactory中的,還記得麼?),之後使用這個CodonBuilder建立了對應的Codon,並把它返回給調用者。
   就這樣,通過CodonNameAttribute,SharpDevelop把addin配置文件的<MenuItem>節點、CodonBulder、MenuItemCodon三部分串起來形成了一個構造Codon的路線。

   我們回過頭來整理一下思路,SharpDevelop進行了下面這樣幾步工作:
      A、建立各個Codon,使用CodonNameAttribute指明它在配置節點中的名稱
      B、DefaultAddInTree的構造函數中調用LoadCodonsAndConditions方法,搜索所有的Codon,根據Codon的CodonNameAttribute建立對應的CodonBuilder加入CodonFactory中。
      C、讀取配置文件,在<Extension>標簽下遍歷所有的節點,根據節點的Name使用CodonFactory建立對應的Codon。
   其中,Codon的CodonNameAttribute、CodonBuilder的CodonName以及<Extension>標簽下XML節點的Name是一致的。對於Condition(條件)的處理也是一樣。
   抱歉,我上網不是很方便也不太會在Blog裡面貼圖(都是為了省事的借口^o^),否則也許更好理解這裡的脈絡關系。

   好了,看到這裡,我們看看SharpDevelop中插件的靈活性是如何體現的。首先,addin配置中的Extension節點下的Codon節點名稱並沒有在代碼中和具體的Codon類聯系起來,而是通過CodonNameAttribute跟Codon聯系起來。這樣做的好處是,SharpDevelop的Codon和XML的標簽一樣具有無限的擴展能力。假設我們要自己定義一個Codon類SplashFormCodon作用是指定某個窗體作為系統啟動時的封面窗體。要做的工作很簡單:首先,在SplashFormCodon中使用CodonNameAttribute指定CodonName為Splash,並且在SplashFormCodon中定義自己需要的屬性。然後,在addin配置文件使用<Splash>標簽這樣寫:



      <Extension path = "/SharpDevelop/ ">
            <Splash id = "MySplashForm" class = "MySplashFormClass"/> 
      </Extension>

   是不是很簡單?另外,對於Condition(條件)的處理也是一樣,也就是說我們也可以使用類似的方法靈活的加入自己定義的條件。

   這裡我有個小小的疑問:不知道我對於設計模式的理解是不是有點小問題,我感覺CodonBuilder類的實現似乎並不如它的類名所暗示的是《設計模式》中的Builder模式,反而似乎應該是Proxy模式,因此我覺得改叫做CodonProxy是不是比較容易理解?各位看官覺得呢?
   另外,雖然稍微麻煩了一小點,不過我覺得配置如果這樣寫會讓我們比較容易和代碼中具體的類關聯起來:



      <Extension path = "/SharpDevelop/ ">
            <Codon name=”Splash” id = "MySplashForm" class = "MySplashFormClass"/> 
      </Extension>


2.2 主線 (AddInTreeSingleton. CreateAddInTree)
   啊~我寫的有點累了。不過還是讓我們繼續AddInTreeSingleton中CreateAddInTree的代碼。
   在建立了DefaultAddInTree的實例後,AddInTreeSingleton在插件目錄中搜索後綴為.addin的文件。還記得在SharpDevelop的Main函數中曾經調用過AddInTreeSingleton. SetAddInDirectorIEs嗎,就是搜索這個傳入的目錄。看來SharpDevelop把在插件目錄中所有後綴為.addin的文件都看做是插件了。



FileUtilityService fileUtilityService = (FileUtilityService)ServiceManager.Services.GetService(typeof(FileUtilityService));

   先學習一下如何從ServiceManager取得所需要的服務,在SharpDevelop中要取得一個服務全部都是通過這種方式取得的。調用GetService傳入要獲取的服務類的類型作為參數,返回一個IService接口,之後轉換成需要的服務。

   搜索插件目錄找到一個addin文件後,調用InsertAddIns把這個addin文件中的配置加入到目錄樹中。



static StringCollection InsertAddIns(StringCollection addInFiles)
  {
   StringCollection retryList  = new StringCollection();
   
   foreach (string addInFile in addInFiles) {
    AddIn addIn = new AddIn();
    try {
     addIn.Initialize(addInFile);
     addInTree.InsertAddIn(addIn);
    } catch (CodonNotFoundException) {
     retryList.Add(addInFile);
    } catch (ConditionNotFoundException) {
     retryList.Add(addInFile);
    } catch (Exception e) {
     throw new AddInInitializeException(addInFile, e);
    } 
   }
   
   return retryList;
  }

   InsertAddIns建立一個對應的AddIn(插件),調用AddInTree的InsertAddIn方法把它掛到插件樹中。在這裡有一個小小的處理,由於是通過Assembly查找和插件配置中Codon的標簽對應的類,而Codon類所在的Assembly是通過Import標簽導入的。因此在查找配置中某個Codon
標簽對應的Codon類的時候,也許Codon類所在的文件是在其他的addin文件中Import的。這個時候在前面支線中講到CodonFactory中查找CodonBuilder會失敗,因此必須等到Codon類所在的addin處理之後才能正確的找到CodonBuilder。這是一個依賴關系的處理問題。
   SharpDevelop在這裡處理的比較簡單,調用InsertAddIns方法的時候,凡是出現CodonNotFoundException的時候,都加入一個retryList列表中返回。在CreateAddinTree處理完所有的addin文件之後,再重新循環嘗試處理retryList列表中的addin。如果某次循環中再也無法成功的加入retryList中的addin,那麼才提示失敗錯誤。

   我們回頭來看看對AddIn的處理。

2.2.1  addIn.Initialize (AddIn的初始化)
   建立了AddIn的實例後,調用Initialize 方法進行初始化。AddIn是對一個.addin文件的封裝,定義在\src\Main\Core\AddIns\AddIn.cs文件中。其中包含了.addin文件的根元素<AddIn>的描述,包括名稱、作者、版權之類的屬性。在<AddIn>節點下包括兩種節點:一個是<Runtime>節點,包含了<Import>指定要導入的Assembly;另外一個是<Extension>節點,指定Codon的擴展點。在AddIn.Initialize方法中,使用XMLDocument對象來讀取對應的addin文件。首先讀取name、author 、copyright之類的基本屬性,之後遍歷所有的ChildNodes(子節點)。

   如果子節點是Runtime節點,則調用AddRuntimeLibrarIEs方法。



   foreach (object o in el.ChildNodes) 
   {
      XmlElement curEl = (XMLElement)o;

      string assemblyName = curEl.Attributes["assembly"].InnerText;
      string pathName     = Path.IsPathRooted(assemblyName) ? assemblyName : fileUtilityService.GetDirectoryNameWithSeparator(path) + assemblyName;
      Assembly asm = AddInTreeSingleton.AddInTree.LoadAssembly(pathName);
      RuntimeLibrarIEs[assemblyName] = asm;
   }

   通過AddInTreeSingleton.AddInTree.LoadAssembly方法把Assembly中所有的Codon和Condition的子類加入對應Factory類中(調用了LoadCodonsAndConditions方法,我們在DefaultAddInTree的構造函數中見過了),並且把該文件和對應的Assembly保存到RuntimeLibrarIEs列表中。

   如果子節點是Extension節點,則調用AddExtensions方法。



      Extension e = new Extension(el.Attributes["path"].InnerText);
      AddCodonsToExtension(e, el, new ConditionCollection());
      extensions.Add(e);

   根據這個擴展點的XML描述建立Extension對象加入到AddIn的Extensions列表中,並通過AddCodonsToExtension方法把其中包括的Codon加入到建立的Extension對象中。Extension對象是AddIn的一個內嵌類,其中一個重要的屬性就是CodonCollection這個列表。AddCodonsToExtension就是把在配置中出現的Codon都加入到這個列表中保存。

   來看看AddCodonsToExtension方法。在代碼中我略過了對Condition(條件)的處理的分析和一些無關緊要的部分,我們把注意力集中在插件的處理。首先是一個 foreach (object o in el.ChildNodes) 遍歷<Extension>下所有的子節點,對於每個子節點的處理如下:



      XmlElement curEl = (XMLElement)o;
      switch (curEl.Name)
      {
      (對條件的處理)
      default:
         ICodon codon = AddInTreeSingleton.AddInTree.CodonFactory.CreateCodon(this, curEl);
         AutoInitializeAttributes(codon, curEl);

         (對codon.InsertAfter 和codon.InsertBefore 的處理,主要是處理codon在列表中的順序問題,這一點在對於MenuItemCodon的處理上比較重要)

         e.CodonCollection.Add(codon);
         if (curEl.ChildNodes.Count > 0) 
         {
            Extension newExtension = new Extension(e.Path + ''/'' + codon.ID);
            AddCodonsToExtension(newExtension, curEl, conditions);
            extensions.Add(newExtension);
         }
         break;
   }

   我們看到了一個期待已久的調用



AddInTreeSingleton.AddInTree.CodonFactory.CreateCodon(this, curEl);

   經過了上文支線2.1代碼中的鋪墊,SharpDevelop使用建立好的CodonFactory,調用CreateCodon方法根據<Extension>下的節點構造出實際的Codon對象,一切盡在不言中了吧。
   e.CodonCollection.Add(codon);把構造出來的Codon對象加入到Extension對象的CodonCollection列表中。
   之後,在形如菜單的這種允許無限嵌套的結構中,SharpDevelop對此進行了處理。如果該節點有嵌套的子節點,那麼構造一個新的Extension對象,遞歸調用AddCodonsToExtension添加到這個Extens
ion對象中。注意一點,這個新構造的Extension對象並不是分開保存在Codon中,而是直接保存在AddIn的擴展點列表中。這樣是為了方便查找,畢竟保存在具體的Codon中也沒有什麼用處,我們可以通過Extension對象的Path屬性得知它在插件樹中的具體位置。

2.2.2 addInTree.InsertAddIn(把AddIn添加到AddInTree中)
   對AddIn的構造完成之後,需要把AddIn的實例對象添加AddInTree中管理。



      addIns.Add(addIn);
      foreach (AddIn.Extension extension in addIn.Extensions)
      {
         AddExtensions(extension);
      }

   在DefaultAddInTree中,保存了兩課樹。一個是根據插件文件的結構形成的樹,每個插件文件作為根節點,往下依次是Extension、Codon節點。addIns.Add(addIn);就是把插件加入到這個樹結構中。另外一個樹是根據Extension的Path+Codon的ID作為路徑構造出來的,每一個樹節點是一個AddInTreeNode類,包含了在這個路徑上的Codon對象。嵌套在這個節點中的Codon在通過它子節點來訪問。在DefaultAddInTree中可以通過GetTreeNode來指定一個路徑獲得插件樹上某一個節點的內容。
   AddExtensions方法很簡單,遍歷Extension中所有的Codon,把Extension的Path+Codon的ID作為路徑,創建這個路徑上的所有節點,並把Codon連接到這個AddInTreeNode上。由於Codon的ID是全局唯一的,因此每一個AddInTreeNode都具有一個唯一的Codon。

3、最後一公裡(Codon和Command的關聯)
   在插件樹的討論中,我們依次把AddIn-Extension-Codon的配置和他們對應的類關聯了起來。不過我們一直沒有涉及到Codon和它包含的Command是如何關聯的。由於這個關聯調用是在插件樹外部的(記得在講述SharpDevelop程序入口Main函數中,提到ServiceManager的方法InitializeServicesSubsystem麼?AddServices((IService[])AddInTreeSingleton.AddInTree.GetTreeNode(servicesPath).BuildChildItems(this).ToArray(typeof(IService))); 這裡就調用了BuildChildItems),因此單獨在這裡說明。實現這個關聯的就是AddInTreeNode的BuildChildItems和BuildChildItem方法以及Codon的BuildItem方法。
   BuildChildItem方法和BuildChildItems方法僅有一字之差,BuildChildItem是根據指定的Codon的ID在所屬AddInTreeNode的子節點下查找包含該Codon的節點並調用該Codon的BuildItem方法;而BuildChildItems則是首先遍歷所屬AddInTreeNode的所有子節點,依次調用各個子節點的Codon的BuildItem方法,之後再調用所屬AddInTreeNode的Codon的BuildItem方法(也就是一個樹的後根遍歷)。
   重點在Codon的BuildItem方法。在AbstractCodon中,這個方法是一個抽象方法,SharpDevelop的代碼注釋中並沒有明確說清楚這個方法是做什麼用的。但是我們可以找一個Codon的實例來看看。例如ClassCodon的BuildItem:



      public override object BuildItem(object owner, ArrayList subItems, ConditionCollection conditions)
      {
         System.Diagnostics.Debug.Assert(Class != null && Class.Length > 0);
         return AddIn.CreateObject(Class);
      }

   調用AddIn的CreateObject,傳入Codon的Class(類名)作為參數,建立這個類的實例。例如這個配置



   <Extension path = "/Workspace/Autostart">
      <Class id = "InitializeWorkbenchCommand" 
          class = "ICSharpCode.SharpDevelop.Commands.InitializeWorkbenchCommand"/>
   </Extension>

   而Codon的中的Class(類名)屬性就是ICSharpCode.SharpDevelop.Commands.InitializeWorkbenchCommand。也就是說,Codon的Class指的是實現具體功能模塊的Command類的名稱。在讀取addin配置中的<Runtime>節的時候,AddInTree把Assembly保存到了RuntimeLibrarIEs中,因此CreateObject方法可以通過它們來查找並建立類的實例。
   各位看官可以再看看MenuItemCodon的實現,同樣是建立了對應的SdMenuCommand。
   這樣,SharpDevelop本身的插件結構可以和具體的對象建立分離開來,實際的對象建立是在各個Codon的BuildItem中進行的。因此我們可以發現在SharpDevelop整個是基礎插件系統部分沒有任何GUI的操作,實現了很好的解耦效果。

4、問題
   好了,本文對插件樹構造的分析到此告一段落。我提一個小小的問題給各位看官思考:在構造插件樹的過程中,如果Codon的某一個節點路徑不存在(也就是說它的依賴項不存在),那麼SharpDevelop會提示失敗並且終止程序運行。可是實際上可能因為部署的原因或者權限的原因,某些Codon的失敗並不會影響整個系統的使用,例如試用版本僅僅提供部分插件給客戶使用,而並不希望系統因此而終止運行。那麼就存在一個Codon依賴項失敗而允許繼續運行的問題。另外,我希望各個插件不在系統啟動的時候全部調入系統,而是在運行期實際調用的時候才調入系統,也就是一個緩存機制,這樣就可以實現系統插件的熱部署。如何修改SharpDevelop的插件系統來實現這兩個功能呢?

   下一回書,應某位網友的要求,分析一下SharpDevelop中的服務。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved