程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> [連載]《C#通訊(串口和網絡)框架的設計與實現》- 9.插件引擎設計,

[連載]《C#通訊(串口和網絡)框架的設計與實現》- 9.插件引擎設計,

編輯:C#入門知識

[連載]《C#通訊(串口和網絡)框架的設計與實現》- 9.插件引擎設計,


目       錄

第九章           插件引擎設計... 2

9.1           框架的契約-接口... 2

9.2           插件的雛形-抽象類... 3

9.3           實現接口... 4

9.4           反射機制... 5

9.5           反射工具類... 8

9.6           小結... 9

 

第九章     插件引擎設計

在介紹《第10章 宿主程序詳細設計》之前對接口和插件的相關內容進行一下整體介紹,在設計宿主程序的時候會用到這些知識,也是宿主程序與插件之間交互的核心內容。

9.1    框架的契約-接口

     插件式框架的宿主程序啟動後,它首先會加載相應的配置文件(例如:設備驅動配置文件等),找到相應的插件程序集,這些程序集以DLL文件格式存在,框架的宿主程序會找到指定的插件類型,由插件引擎依據插件類型(例如:IRunDevice)生成對象實例,由框架的宿主程序的管理器對插件實例進行管理和調度。

    一個插件程序集可能包括多個插件類型,那麼框架宿主程序是如何識別這些類型是否為要加載的插件呢?每個插件對象都有一個身份標識-接口,這個標識在框架設計中被稱為“通訊契約”。接口可以被看作是一種定義了必要的方法、屬性和事件的集合,因此宿主程序就可以通過這種契約來生成具體的實例對象,並對其他組件或接口公開可操作的對象。

    插件式框架作為一個高聚合低耦合的平台,它的功能定義與功能實現之間是分離的。只要符合插件規范的二次開發組件都可以掛載到框架平台中,而它並不並心這些組件的具體功能。當然,框架平台提供了一些必要的信息、機制來保證這些組件能夠正常實現二次開發的功能。

    在具有多個邏輯層次的結構設計中,各層之間的通信大多通過接口來實現,接口不會輕易改變,如果一個層的功能發生變化,不會影響其他層;只要正常實現了接口的組件功能,那麼程序的運行就沒有問題。這種做法使得各層之間的相互影響降低到最低,總之,接口在多業務層級中能夠更好的解耦。

    在大部分功能性的編程和設計工作中,很少需要考慮“接口(interface) ”的情況,如果我們僅僅滿足通過控件的方式在IDE上編程和使用.NET Framework中一般的類庫,可能永遠不會在程序中使用到接口,即使在C#等面向對象語言的語法書中讀者會無數次看到過這個詞,也只是完成一般性的功能,並未掌握面向對象編程的核心思想。

     接口是一般行為的定義和契約。如貓和狗等動物,只需要將一般性的、公共性的屬性、動作等定義在接口裡,例如:有眼睛、可以吃東西等。盡管不同動物之間存在很大差異,但是接口並不考慮它們各自的特性或功能的差異,例如:什麼顏色的眼睛、吃什麼東西等。它只關心這些類型都必須實現接口定義的所有功能,而實現了這個接口就可以被看作是一種動物。

    因此,接口的兩個主要的作用是:

n  定義多個類型都需要的公共方法、屬性。

n  作為一種不可實例化的類型存在。

繼承接口實現定義的方法、屬性等,實際上是實現了一種策略。

9.2    插件的雛形-抽象類

接口與抽象類非常相似,例如兩者都不能new一個實例對象,卻都可以作

為一種契約和定義被使用。但是接口和抽象類有本質的不同,這些不同包括:

n  接口沒有任何實現部分,但是抽象類可以繼承接口後部分實現代碼。

n  接口沒有字段,但是抽象類可以包含字段。

n  接口可以被結構(Struct)繼承,但是抽象類不行。

n  抽象類有構造函數和析構函數。

n  接口僅能繼承自接口,而抽象類可以繼承自其他類和接口。

n  接口支持多繼承,抽象類僅支持單根繼承。

在MSDN的相關內容中,給出了如下關於接口與抽象類的建議:

n  如果預計要創建組件的多個版本,則創建抽象類。抽象類提供簡單易行的方法來控制組件版本。通過更新基類,所有繼承類都隨更改自動更新。另一方面,接口一旦創建就不能更改,如果要更新接口的版本,必須創建一個全新的接口。

n  如果創建的功能將在大范圍的全異對象間使用,則使用接口。抽象類應主要用於關系密切的對象,而接口最適合為不相關的類提供通用的功能。

n  如果要設計小而簡練的功能模塊,應該使用接口。如果要設計大的功能單元,則應該使用抽象類。

n  如果要在組件的所有實現間提供通用的已實現功能,應該使用抽象類。抽象類允許部分實現類,而接口不包含任何成員的實現。

9.3    實現接口

接口和抽象類都可以作為“通信契約”,為子類提供規范。下面定義一個接口和抽象類。

//定義一個接口
public interface IMyInterface
{
       void Action(int type);
       string Method(int para);
}

//定義一個抽象類
public abstract class BaseAbstract:IMyInterface

{
       public abstract void Action(int type); //繼承此類抽象類時必須實現這個方法。

       public string Method(int para)         //實現這個方法
       {
              return para.ToString();
       }
}

繼承接口的話,需要實現全部定義的方法或屬性,如下代碼:

public class MyClass1:IMyInterface
{
       public void Action(int type)
       {
              Console.WriteLine(type.ToString());
       }
      
       public string Method(int para)        
       {
              return para.ToString();
       }
}

繼承抽象類的話,只需要實現抽象類沒有實現的方法或屬性,一般為抽象方法或屬性,如下代碼:

public class MyClass2:BaseAbstract
{
       public void Action(int type)   //繼承抽象類,只需要實現這個函數。
       {
              Console.WriteLine(type.ToString());
       }
}

9.4    反射機制

    有了設備驅動或插件,還不能掛載到框架平台的宿主程序中。我們考慮的問題是:已經有了任意多個類型插件程序集,框架平台如何從程序集中根據類型定義在內存中生成插件對象?

   回顧普通情況下程序引用其他程序集組件的過程。首先,需要使用“添加引用”對話框加載程序集。然後,通過using關鍵字引用命名空間。最後,在命令空間下找到相應的類,並new出來一個實例。這是一種靜態加載程序集的方式。

   在插件式應用框架中,這種方法並不適合。宿主程序在編譯時並不知道它將要處理哪些程序集,更沒有辦法靜態的將插件類型通過using關鍵字引入,這些都是在運行時才能獲得的信息。在這樣的情況下,也無法使用靜態方法和new關鍵字來生成一個類型實例。而是需要在運行時獲得相關信息動態加載程序集,這個過程被稱為反射。

   反射是動態發現類型信息的一種能力,它類似後期綁定,幫助開發人員在程序運行時利用程序集信息動態使用類型,這些信息在編譯時是未知的,反射還支持更高級的行為,如能在運行時動態創建新類型,並調用這些類型的方法等。

    JIT編譯器在將IL代碼編譯成本地代碼時,會查看IL代碼中引用了那些類型。在運行時,JIT編譯器利用程序集的TypeRef和AssemblyRef元數據表的記錄項來確定哪一個程序集定義了引用的類型。在 AssemblyRef元數據記錄項中記錄了程序集強名稱的各個部分—包括名稱,版本,公鑰標記和語言文化。這四個部分組成了一個字符串標識。JIT編譯 器嘗試將與這個標識匹配的程序集加載到當前的AppDomain中。如果程序集是弱命名的,標識中將只包含名稱。

   .NET Framework中,為了實現動態加載,需要熟悉Assembly、Type和Activator等工具類的方法。框架平台主要使用了Assembly工具類,這個類中包括Load、LoadFrom和LoadFile。

1.      Assembly的Load方法

   在內部CLR使用Assembly的Load方法來加載這個程序集,這個方法與Win32的LoadLibray等價。在內部,Load導致CLR對程序集應用一個版本重定向策略。並在GAC中查找程序集,如果沒有找到,就去應用程序的基目錄,私有路徑目錄和codebase指定的位置查找。如果是一個弱命名程序集,Load不會向程序集應用重定向策略,也不會去GAC中查找程序集。如果找到將返回一個Assembly的引用,如果沒有找到則拋出FileNotFoundException異常。注意:Load方法如果已經加載一個相同標識的程序集只會簡單的返回這個程序集的引用,而不會去創建一個新的程序集。

大多數動態可擴展應用程序中,Assembly的Load方法是程序集加載到AppDomain的首選方式。這種方式需要指定程序集的標識字符串。對於弱命名程序集只用指定一個名字。

2.Assembly的LoadFrom方法

    當我們知道程序集的路徑的場合,可以使用LoadFrom方法,它允許傳入一個Path字符串,在內部,LoadFrom首先調用AssemblyName的靜態方法GetAssemblyName。這個方法打開指定的文件,通過AssemblyRef元數據表提取程序集的標識,然後關閉文件。隨後,LoadFrom在內部調用Assembly的Load方法查找程序集。到這裡,他的行為和Load方法是一致的。唯一不同的是,如果按Load的方式沒有找到程序集,LoadFrom會加載Path路徑指定的程序集。另外,Path可以是URL。

3.Assembly的LoadFile方法

    這個方法初一看和LoadFrom方法很像。但LoadFile方法不會在內部調用Assembly的Load方法。它只會加載指定Path的程序集,並且這個方法可以從任意路徑加載程序集,同一程序集如果在不同的路徑下,它允許被多次加載,等於多個同名的程序集加載到了AppDomain中,這一點和上面的兩個方法完全不一樣。但是,LoadFile並不會加載程序集的依賴項,也就是不會加載程序集引用的其他程序集,這會導致運行時找不到其他參照DLL的異常。要解決這個問題,需要向AppDomain的AssemblyResolve事件登記,在回調方法中顯示加載引用的程序集。類似於這樣:

AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
       if (args.Name != null)
       {
              return Assembly.LoadFrom(string.Format("{0}\\plugin\\{1}.dll", Application.StartupPath, new AssemblyName(args.Name).Name));
       }
       return null;
}

      特別注意:要測試LoadFile有沒有加載引用的DLL,切不可將DLL拷貝到應用程序的根目錄下測試,因為該目錄是CLR加載程序集的默認目錄,在這個目錄中如果存在引用的DLL,它會被加載,造成LoadFile會加載引用DLL的假象。可以在根目錄下新建一個子目錄如plugin,把引用的dll拷貝到這裡面進行測試。

     反射機制也有它的缺點:安全性和性能方面。但是,框架平台在啟動的時候、以及增加新設備驅動(插件)的時候需要使用反射,一旦加載到宿主程序中,與靜態引用程序集沒有本質區別,都是寄存在內存中。

9.5    反射工具類

插件式框架平台使用反射掛載設備驅動,在宿主程序中運行,需要一個專用的工具類來完成相關功能。代碼定義如下:

/// <summary>
/// 一個輕便的 IObjectBuilder 實現
/// </summary>
public class TypeCreator : IObjectBuilder
{
       public T BuildUp<T>() where T : new()
       {
              return Activator.CreateInstance<T>();
       }

       public T BuildUp<T>(string typeName)
       {
              return (T)Activator.CreateInstance(Type.GetType(typeName));
       }

       public T BuildUp<T>(object[] args)
       {
              object result = Activator.CreateInstance(typeof(T),args);
              return (T)result;
       }

       /// <summary>
       /// 框架平台主要使用了這個函數。
       /// </summary>
       /// <typeparam name="T"></typeparam>
       /// <param name="assemblyname"></param>
       /// <param name="instancename"></param>
       /// <returns></returns>
       public T BuildUp<T>(string assemblyname, string instancename)
       {
              if (!System.IO.File.Exists(assemblyname))
              {
                     throw new FileNotFoundException(assemblyname + " 不存在");
              }
              System.Reflection.Assembly assmble = System.Reflection.Assembly.LoadFrom (assemblyname);
              object tmpobj = assmble.CreateInstance(instancename);
              return (T)tmpobj;
       }

       public T BuildUp<T>(string typeName, object[] args)
       {
              object result = Activator.CreateInstance(Type.GetType(typeName), args);
              return (T)result;
       }
}

9.6    小結

    下一章節介紹宿主程序詳細設計,需要對反射機制有一定的了解,並且會使用到上面的工具類,並在此基礎上進行擴展。

    框架平台就要完全了,只需要一小步了。

 

作者:唯笑志在

Email:[email protected]

QQ:504547114

.NET開發技術聯盟:54256083

文檔下載:http://pan.baidu.com/s/1pJ7lZWf

官方網址:http://www.bmpj.net

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