AppDomain 動態加載及數據交互

前一個項目,有機會學了AppDomian基本概念。把學習心得寫下來。範例可以在這裏下載

AppDomain的作用

         AppDomain 也叫應用程序域,是CLR創建的,用來規範代碼的執行範圍,並提供獨立的錯誤隔離機制。因此,每個應用程序包可以包含一個或多個AppDomain,每個AppDomain能夠獨立卸載及管理Assembly。

         因爲AppDomain也是獨立存在互不影響,而且創建一個AppDomain和跨AppDomain的開銷都比進程要低得多,所以常常使用獨立AppDomain來提供服務,避免由於程序某部分出錯而導致整個程序崩潰。

示例簡述

        通過調用不同AppDomain中SayHi的對象 實現了 中文,英文,同時使用中英文的SayHi方法,以及異常問題。

  1. MultiAppDomain 項目:是默認程序域的所在項目。用於創建副AppDomain,並且調用副AppDomain裏面的服務實現SayHi的調用
  2.  Interface項目。只有一個接口,用於規範SayHi的調用方式。
  3.  SayHiChinese::實現中文SayHi
  4.  SayHiEnglish: 實現英文SayHI
  5.  SayHiBoth:調用副AppDomain中的實現中英文SayHi
  6. SayHiException:在條用過程中拋出一個異常,並且檢查副AppDomain中的狀態。 

AppDomain的基本方法

        默認AppDomain就是在應用程序一開始運行所在的程序域。而副AppDomain指的就是在默認程序域或者其他程序域下創建的程序域。

  •  創建一個AppDomain。

             無論在程序域中或者副AppDoman中都可以創建新的AppDomain,而這個程序域就是受到當前程序域管理的。如:             

AppDomain chineseAppDomain= AppDomain.CreateDomain("chinese"); 

        輸入參數味AppDomain的名稱,這個名稱可以重複。也就是你可以有多個叫做chinese的的程序域
默認地 chineseAppDomain. BaseDirectory指向的目錄是默認程序域的目錄。當程序運行的時候,需要元數據,會後首先從這個目錄開始查找。

  • 卸載一個程序域。

 

 AppDomain.Unload(aAppDomainInstance);

 
        默認程序域是不能被卸載的,否則回拋出CannotUnloadAppDomainException 錯誤。
       如果是副程序域自己卸載自己,哪麼拋出內部拋出System.Threading.ThreadAbortException 異常,但是在外部可以捕獲System.AppDomainUnloadedException。

  • 獲取當前的AppDomain
AppDomain dom = AppDomain.CurrentDomain.

        如果當前在默認程序域中,哪麼就獲取默認程序域對象,如果在其他副AppDomain中,哪麼就獲取各自的AppDomain。

         創建第一個例子。在項目MultiAppDomain,打開文件Program.cs ,在Main 函數中找到

AppDomain chineseAppDomain = AppDomain.CreateDomain("chinese");
AppDomain englishAppDomain 
= AppDomain.CreateDomain("english");
AppDomain bothAppDomain 
= AppDomain.CreateDomain("both");
AppDomain exceptionDomain 
= AppDomain.CreateDomain("exception");
  • AppDomain之間值傳遞

         只有繼承了System.MarshalbyRefObject或者聲明屬性SerializableAttribute的類的實例,才能在AppDomain之間傳遞。即:

[Serializable]
Public 
class A
{}
 
Public Class B:System.MarshalbyRefObject
{}

       假設 A的實例a 和 B的實例b,需要從AppDomain X傳遞到AppDomain Y中。哪麼 a實例相當於對象拷貝。在 AppDomain Y中任何對 a的操作都不會影響倒 AppDomainX中的a。而b是通過跨域的透明代理遞到,相當於值引用。因此 在 AppDomain Y中,任何對b的操作對於對 AppDomain X中的b產生影響。參考下圖:

在AppDoain中,可以通過AppDomain.SetData 和 GetData方法進行存取操作 

AppDomain的動態導入

        要實現在默認AppDomain中實現對副AppDomain的導入控制,必須獲取副AppDomain中的RemotLoader的實例如下圖

 

       導入前,首先打開 MultiAppDomain項目中的RemoteLoader.cs 文件,查看一下Load方法。

public class RemoteLoader : MarshalByRefObject
{
        
private Dictionary<string, Assembly> assemblies = new Dictionary<string, Assembly>();
        
public void Load(string fullPath)
        {
            
byte[] fsContent;
            
using(FileStream fs= File.OpenRead(fullPath))
            {
                fsContent 
= new byte[fs.Length];
                fs.Read(fsContent, 
0, fsContent.Length);                
            }
            Assembly assembly 
= Assembly.Load(fsContent);        
            assemblies.Add(assembly.GetName().Name, assembly);
        }
}

        在Load方法內,使用byte導入Assembly,這樣做可以防止Assembly文件獨佔。如果使用文件路徑方式導入即Assembly.Load(filePath), 那麼文件在程序運行的過程中是不能被更改的。


       在這裏說個題外話, 在寫這個例子的開始時,我是使用路徑導入方式,開始一直都很順利。當實現BothSayHi中SayHi的時候,卻拋出轉換異常的錯誤,說引用的透明代理(__TransparentProxy)對象不能被轉換成爲ISay接口類型。但是把代碼粘貼在Main方法中就正常。後來試過N種方法,包括改爲抽象類、類等都還是不行。正在百思不得其解,無意中把load導入改爲byte導入就正常了。

        我個人認爲,是因爲在Main方法中,自身獨佔了Debug目錄下的Interface.dll。而其他副AppDomain是直接獲取Interface項目下的Dll。因此在Main方法中的調用是成功的,因爲元數據的所屬文件只有自己佔用。但是在BothSayHi中的Interface.dll是和其他副AppDomain一起使用的。因此在BothSayHi中,在轉換的時候,剛好需要獲取Interface的元數據,而interface剛好被其他副AppDomain佔用而無法讀取元數據。因此獲取不了元數據,只好拋出轉換異常。這個只是我個人的想法,希望有高手指出問題的本質。

      好了言歸正傳 然後RemoteLoader需要在副AppDomain中創建,並在默認AppDomain引用。
       在項目MultiAppDomain,文件Program.cs ,在Main 函數中找到:

RemoteLoader chineseLoader =
                (RemoteLoader) chineseAppDomain.CreateInstanceAndUnwrap(
"MultiAppDomain""MultiAppDomain.RemoteLoader");

也可以寫成:

RemoteLoader chineseLoader =
                (RemoteLoader) chineseAppDomain.CreateInstance (
"MultiAppDomain""MultiAppDomain.RemoteLoader").Unwrap;

        如果使用第二種方法創建,那麼返回的是System.Runtime.Remoting.ObjectHandle 對象。這個對象在Msdn的定義是:包裝按值封送對象引用,從而使它們可以通過間接尋址返回。因此在默認AppDomain中並不需要它的相關元數據(因此無需導入相關Assembly),直到使用方法unwarrp. 默認AppDomain纔開始導入相關的元數據。
        Unwrap後對象是一個透明代理,再進行強制轉換。獲得RemoteLoader代理後,爲每個副域導入相關Assembly,如下:

internal class Program
{
 
string chineseDll = @"../../../SayHiChinese/bin/Debug/SayHiChinese.dll";
 
string interfaceDll = @"../../../Interface/bin/Debug/Interface.dll";
//省略代碼
private static void Main(string[] args)
{
//省略代碼
chineseLoader.Load(interfaceDll);
chineseLoader.Load(chineseDll);
chineseLoader.CreateService(
"sayHi""SayHiChinese""SayHiChinese.SayHi");
//省略代碼
}
}

        留意下chineseLoader.Load(interfaceDll),這句是可以省略的(開始不明原因,興奮了好一陣子,以爲.net有這麼高的只能)。首先,在MultiAppDomain中所在的默認目錄已經存在這個文件(因爲項目顯示引用了Interface項目),然後在創建chineseAppDomain的時候,我並沒有指定副AppDomain的BaseDirectory,因此BaseDirecotry默認指向MultiAppDomain的運行目錄。而在這MultiAppDomain項目中,我們引用了Interface,因此在路徑中也包含了Interface.Dll,所以當我們調用chineseLoader.CreateService()創建SayHiChinese.SayHi實例時,CLR自動從MultiAppDomain目錄中,自動導入Interface.Dll。

因爲BothSayHi是需要其他兩個實例的支持才能完成,因此創建代碼就需要輸入chinese和English的實例的

bothLoader.Load(interfaceDll);
bothLoader.Load(bothDll);
bothLoader.SetServiceFrom("chinese", chineseLoader.GetService("sayHi"));
bothLoader.SetServiceFrom(
"english", englishLoader.GetService("sayHi"));
bothLoader.CreateService(
"sayHi""BothSayHi""BothSayHi.SayHi");

在BothSayHi中實現調用chinese和english的服務:
bothSayHi的方法

public string Say()
{  
            ISayHi chinese
=(ISayHi)AppDomain.CurrentDomain.GetData("chinese");
            ISayHi english 
= (ISayHi) AppDomain.CurrentDomain.GetData("english");
            Console.WriteLine(chinese.Say() 
+ " " + english.Say());
            
return chinese.Say() + " " + english.Say();
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章