.Net動態加載程序集

  我們先來看.net中與本幾個重要的概念:

1.         應用程序域: 由 AppDomain 對象來表示,爲執行託管代碼提供隔離、卸載和安全邊界。多個應用程序域可以在一個進程中運行;但是,在應用程序域和線程之間沒有一對一的關聯。多個線程可以屬於一個應用程序域,儘管給定的線程並不侷限於一個應用程序域,但在任何給定時間,線程都在一個應用程序域中執行。應用程序域通過使用 CreateDomain 方法來創建。AppDomain 實例用於加載和執行程序集 (Assembly)。當不再使用 AppDomain 時,可以將它卸載。AppDomain 類實現一組事件,這些事件使應用程序可以在加載程序集、卸載應用程序域或引發未處理的異常時進行響應。
 
2.         程序集是: .NET 框架應用程序的生成塊;程序集構成了部署、版本控制、重複使用、激活範圍控制和安全權限的基本單元。程序集是爲協同工作而生成的類型和資源的集合,這些類型和資源構成了一個邏輯功能單元。程序集爲公共語言運行庫提供它要用於識別類型實現的信息程序集旨在簡化應用程序部署並解決在基於組件的應用程序中可能出現的版本控制問題,程序集可以是靜態的或動態的。靜態程序集可以包括 .NET 框架類型(接口和類),以及該程序集的資源(位圖、JPEG 文件、資源文件等)。靜態程序集以 PE 文件格式存儲在磁盤上。您還可以使用 .NET 框架來創建動態程序集,動態程序集直接從內存運行並且在執行前不存儲到磁盤上。您可以在執行動態程序集後將它們保存在磁盤上。
 
3.         運行庫如何定位程序集:若要成功地部署 .NET 框架應用程序,必須瞭解公共語言運行庫如何定位和綁定組成應用程序的程序集。默認情況下,運行庫試圖與生成應用程序使用的程序集的原版本綁定。這種默認行爲可以由配置文件設置覆蓋。在試圖定位程序集並解析程序集引用時,公共語言運行庫將執行若干個步驟。以下幾節中對每個步驟進行了解釋。當描述運行庫如何定位程序集時,通常使用“探測”一詞;它是指基於名稱和區域性定位程序集時使用的一組試探法。(使用包含在 .NET 框架 SDK 中的程序集綁定日誌查看器 (Fuslogvw.exe),來查看日誌文件中的綁定信息)。當運行庫試圖解析對另一個程序集的引用時,就開始進行定位並綁定到程序集的進程。該引用可以是靜態的,也可以是動態的。在生成時,編譯器在程序集清單的元數據中記錄靜態引用。動態引用是由於調用各種方法而動態構造的,例如System.Reflection.Assembly.Load 方法。引用程序集的首選方式就是使用完全引用,包括程序集名稱、版本、區域性和公鑰標記(如果存在)。運行庫就會按照本節後面描述的步驟,使用這些信息來定位程序集。無論引用是對靜態程序集的引用還是對動態程序集的引用,運行庫均使用相同的解析過程。還可通過向調用方法僅提供有關程序集的部分信息的方式(例如僅指定程序集名稱),對程序集進行動態引用。在這種情況下,僅在應用程序目錄下搜索程序集,不進行其他檢查。您可以使用不同加載程序集方法中的任何方法(例如 System.Reflection.Assembly.Load 或 AppDomain.Load)進行部分引用。如果希望運行庫在全局程序集緩存和應用程序目錄下檢查引用的程序集,可以用 System.Reflection.Assembly.LoadWithPartialName 方法指定部分引用。有關部分綁定的更多信息,請參閱部分程序集引用。最後,可以使用諸如 System.Reflection.Assembly.Load 之類的方法進行動態引用並只提供部分信息;然後在應用程序配置文件中用 <qualifyAssembly> 元素限定該引用。該元素使您可以在應用程序配置文件中而不是在代碼中提供完全引用信息,包括名稱、版本、區域性和公鑰標記(如果適用)。如果要在應用程序目錄外完全限定對某個程序集的引用,或者如果要引用全局程序集緩存中的程序集,但又希望方便地在配置文件中而不是在代碼中指定完全引用,就可以採用這一技術。注意 此類型的部分引用不應用於幾個應用程序共享的程序集。因爲配置設置是應用於每一應用程序而不是每一個程序集的,所以使用此類型部分引用的共享程序集將要求使用該共享程序集的每一應用程序都要在其配置文件中具有限定信息。
運行庫使用以下步驟來解析程序集引用:
1.通過檢查適用的配置文件(包括應用程序配置文件、發行者策略文件和計算機配置文件),確定正確的程序集版本。如果配置文件位於遠程計算機上,則運行庫必須首先定位和下載應用程序配置文件。
2.檢查程序集名是否以前已被綁定,如果已綁定,則使用以前加載的程序集。
3.檢查全局程序集緩存。如果在其中找到了程序集,則運行庫使用該程序集。
4.按以下步驟探測程序集:
a.如果配置策略和發行者策略不影響原始引用,並且綁定請求是使用 Assembly.LoadFrom 方法創建的,則運行庫檢查位置提示。
b.如果在配置文件中找到了代碼基,則運行庫只檢查該位置。如果該探測失敗,則運行庫確定綁定請求已失敗,並且不再進行其他的探測。
c.使用探測部分中描述的試探法探測程序集。如果探測後沒有找到程序集,則運行庫請求 Windows 安裝程序提供程序集。它用作一個即需即裝功能。
注意 不對沒有強名稱的程序集進行版本檢查,並且運行庫也不在全局程序集緩存中檢查沒有強名稱的程序集。
 
正式開始本文:
在 .NET 框架中,將程序集加載至應用程序域的方法有幾種。每種方法使用不同的類。
您可以使用下面的重載方法將程序集加載至應用程序域: 
  1. System.AppDomain 類包含幾種重載的 Load 方法。儘管這些方法可用於將任何程序集成功地加載至當前的或新的應用程序域,但它們主要還是用於 COM 交互操作。您也可以使用 CreateInstance 方法加載程序集。
  2. System.Reflection.Assembly 類包含兩種靜態重載方法:Load 和 LoadFrom。這兩種方法因加載上下文而異。
簡單例題:講解了在一個.exe文件中調用另一個.exe文件的方法
using System;
namespace dy_loadAsse

{
     
class testclass
     
{
          [STAThread]
         
static void Main(string[] args)
         
{
              OutUse test
=new OutUse();
               test.Output();
              Console.ReadLine();

         }

     }

     
class OutUse
     
{
         
public  OutUse()
         
{
         }

         
public void Output()
         
{
              Console.WriteLine(
"test dy load assembly");
         }

     }

}

 
以上編譯成功。爲dy_loadAsse.exe,執行顯示:
test dy load assembly
放在與下面生成的loadexe.exe於同一目錄下。
 
文件二:
using System;
using System.Reflection;
namespace Use_dy_Load_Assembly
{
     
class LoadExe
     
{
          [STAThread]
         
static void Main(string[] args)
         
{
              
// Use the file name to load the assembly into the current application domain.
              Assembly a = Assembly.LoadFrom("dy_loadAsse.exe");
              Type [] types2 
= a.GetTypes();
              
foreach (Type t in types2)
              
{
                   Console.WriteLine (t.FullName);
              }

              
//Get the type to use.
              
//Type myType = a.GetType("OutUse"); 這樣寫老是出錯
              Type myType = a.GetType("dy_loadAsse.OutUse");
             Console.WriteLine (myType.FullName);
              
//Get the method to call.
              MethodInfo mymethod = myType.GetMethod("Output");
//            //Create an instance.
            Object obj = Activator.CreateInstance(myType);
//            //Execute the adnamemethod method.
            mymethod.Invoke(obj,null);
              
//執行結果爲test dy load assembly
              
//以下是調用dy_loadAsse.exe中的Main方法,出現錯誤
//                     Type myType = a.GetType("dy_loadAsse.testclass");
//                    Console.WriteLine (myType.FullName);
//                     //Get the method to call.
//                    MethodInfo mymethod = myType.GetMethod("Main");
//            ////          //Create an instance.
//                        Object obj = Activator.CreateInstance(myType);
//            ////          //Execute the adnamemethod method.
//                         mymethod.Invoke(obj,null);     
              Console.ReadLine();
         }

     }

}

 
實際上不管你是.exe或dll 組成的程序集 都可以被加載
 
自定義綁定
 
除了由編譯器隱式地用來進行晚期綁定之外(指virtual方法,接口等相關實現的綁定),反射還可以在代碼中顯式地用來完成晚期綁定。 公共語言運行庫支持多種編程語言,但這些語言的綁定規則各不相同。在早期綁定的情況下,代碼生成器可以完全控制此綁定。但是,當通過反射進行晚期綁定時,必須用自定義綁定來控制綁定。Binder 類提供了對成員選擇和調用的自定義控制。
 
利用自定義綁定,您可以在運行時加載程序集,獲取有關該程序集中類型的信息,然後對該類型調用方法或訪問該類型的字段或屬性。如果您在編譯時(例如當對象類型依賴於用戶輸入時)不知道對象的類型,就可以使用這種方法。
using System;
namespace dy_loaddll
{
     
public class HelloWorld 
     
{
         
// Constant Hello World string.
          private const String m_helloWorld = "Hello World";
         
// Default public constructor.
         public HelloWorld() 
         
{
         }

         
// Print "Hello World" plus the passed text.
         public void PrintHello(String txt) 
         
{
              
// Output to the Console.
              Console.WriteLine(m_helloWorld + " " + txt);
         }

     }

}


編輯生成dy_loaddll.dll,與 LoadExe.exe在同一的目錄下面。

using System;
using System.Reflection;
namespace Use_dy_Load_Assembly
{
     
class LoadExe
     
{
          [STAThread]
         
static void Main(string[] args)
         
{
              
// Use the file name to load the assembly into the current application domain.
              Assembly a = Assembly.LoadFrom("dy_loaddll.dll");
              Type [] types2 
= a.GetTypes();
              
foreach (Type t in types2)
              
{
                   Console.WriteLine (t.FullName);
              }

              
//Get the type to use.
//            Type myType = a.GetType(""); 這樣寫老是出錯因爲上面的dll加了命名空間
                       Type myType = a.GetType("dy_loaddll.HelloWorld");
                      
//   Type myType = a.GetType("HelloWorld");
                            Console.WriteLine (myType.FullName);
//                          //Get the method to call.
              MethodInfo printMethod = myType.GetMethod("PrintHello");
              
// Create an instance of the HelloWorld class.
              Object obj = Activator.CreateInstance(myType);
              
// Create the args array.
              Object[] test = new Object[1];
              
// Set the arguments.
              test[0= "From CSharp Late Bound";
              
// Invoke the PrintHello method.
              printMethod.Invoke(obj, test);   
              Console.ReadLine();
         }

     }

}

當然我們不禁要問題,如果方法重載,如果是屬性。又該怎麼辦??我們先看看一般的反射的動態方法查找
下面爲ms自帶的例子:

public class A
{
   
public virtual int method () {return 0;}
}

public class B
{
   
public virtual int method () {return 1;}
}

class Mymethodinfo
{
   
public static int Main()
   
{
      Console.WriteLine (
" Reflection.MethodInfo");
      A MyA 
= new A();
      B MyB 
= new B();
      
//Get the Type and MethodInfo
      Type MyTypea = Type.GetType("A");
      MethodInfo Mymethodinfoa 
= MyTypea.GetMethod("method");
      Type MyTypeb 
= Type.GetType("B");
      MethodInfo Mymethodinfob 
= MyTypeb.GetMethod("method");
      
//Get and display the Invoke method
      Console.Write(" First method - " + MyTypea.FullName +" returns " + Mymethodinfoa.Invoke(MyA, null));
      Console.Write(
" Second method - " + MyTypeb.FullName +" returns " + Mymethodinfob.Invoke(MyB, null));
      
return 0;
   }

}

下面是用接口查詢方法,實例創建對象,再執行實例對象


using System;
public interface  IPoint 
{
      
// return  now     class of  shix    interface
        string  ReturnNowClass();
}
    

上面文件爲 ClassSuc.cs 編輯爲ClassSuc.dll  。

using System;
namespace ClassLib1
{
     
public class Class1:  IPoint
     {
         
public Class1()
         {
         }
         
public string ReturnNowClass()
         {
            
return  "weclone  Execute ClassLib1   Class1";
         }
     }
}

將ClassSuc.dll的也添加到上面文件,Class1實現了Ipoint接口編輯文件爲ClassLib1.dll

using System;
namespace ClassLib1
{
     
public class Class1:  IPoint
     
{
         
public Class1()
         
{
         }

         
public string ReturnNowClass()
         
{
            
return  "weclone  Execute ClassLib1   Class1";
         }

     }

}

 將ClassSuc.dll的也添加到上面文件,Class1實現了Ipoint接口編輯文件爲ClassLib1.dll

using System;
namespace ClassLib2
{
     
public class Class2:IPoint
     
{
        
public Class2()
         
{
         }

         
public string ReturnNowClass()
         
{
              
return  "ClassLib2"+"Class2"+"weclone";
         }

     }

}

也許你已經看和做的不厭煩了,你可能要要問,你到底想講什麼????注意,將上面的三個dll  copy 在 同一路徑下 這裏爲“C:/test”

using System;
using System.Reflection;
class LoadExe
     
{
         [STAThread]
         
static void Main(string[] args)
         
{
         
// Use the file name to load the assembly into the current application domain.
              Assembly b;
              b 
= Assembly.LoadFrom(@"C:/test/ClassSuc.dll");
              Type[] mytypes 
= b.GetTypes();
              
// show b中的接口IPoint
              foreach (Type t in mytypes)
              
{
                   Console.WriteLine (t.FullName);
              }

              MethodInfo Method 
= mytypes[0].GetMethod("ReturnNowClass");
              
//Get the method to call.
                            Assembly a ;
                            
string  k=Console.ReadLine();
              
//輸入大於10時,調用ClassLib1.dll的方法 否則調用ClassLib2的方法
                            if(Convert.ToInt32(k)>10)
                                 a 
= Assembly.LoadFrom(@"C:/test/ClassLib1.dll");
                            
else
                                 a 
= Assembly.LoadFrom(@"C:/test/ClassLib2.dll");
              Type[] types 
= a.GetTypes();
              
// show b中的ClassLib1.Class1或 ClassLib2.Class2
              foreach (Type t in  types)
              
{
                   Console.WriteLine (t.FullName);
              }

         
// Create an instance of the HelloWorld class.
        Object obj = Activator.CreateInstance(types[0]);
              
// Invoke the  method.
              Console.WriteLine(Method.Invoke(obj, null));   
              Console.ReadLine();
         }

     }

執行效果爲:
Ipoint
這時要求輸入 輸入
13
繼續執行顯示
ClassLib1.Class1
weclone  Execute ClassLib1   Class1
要求輸入時 輸入
5
繼續執行顯示
ClassLib2.Class2
weclone  Execute ClassLib2   Class2
實現了什麼,通過接口動態加載程序集。
意義:反射機制實現動態插拔,只需更改配置文件和XCOPY相應的組件,
可以無須編譯就直接定製出一個特定系統

缺點: 性能衝擊 速度慢
有的人還要問,既然能夠動態加載程序集
那如何顯示卸載程序集  CLR不支持卸載程序集  但可以卸載AppDomain包含的所有的程序集。AppDomain.Unload 方法 卸載指定的應用程序域。
本還想寫一文章講述動態程序集 但自己理解不夠,又覺得意義不大所以把那些東西,自己也不想學那些東西(太浮躁的表現),所以提到這裏來。
動態程序集是編譯器或工具在運行時發出元數據和 MSIL,並可在磁盤上生成可移植可執行 (PE) 文件 (不同於上面的那個動態加載程序集)
在運行時定義程序集,然後運行這些程序集並/或將它們保存到磁盤。
在運行時定義新程序集中的模塊,然後運行這些模塊並/或將它們保存到磁盤。
在運行時定義類型,創建這些類型的實例,並調用這些類型的方法。
程序集---》 模塊---》類型—》實例的方法

 

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