C# 有關Assembly.Unload詳解
CLR 產品單元經理(Unit Manager) Jason Zander 在前幾天一篇文章 Why isn't there an Assembly.Unload method? 中解釋了為什麼 CLR 中目前沒有實現類似 Win32 API 中 UnloadLibraryhttp://www.cppcns.com0;函式功能的 Assembly.Unload 方法。
他認為之所以要實現 Assembly.Unload 函式,主要是為了回收空間和更新版本兩類需求。前者在使用完 Assembly 後回收其佔用資源,後者則解除安裝當前版本載入更新的版本。例如 ASP.NET 中對頁面用到的 Assembly 程式的動態更新就是一個很好的使用示例。但如果提供了 Assembly.Unload 函式會引發一些問題:
1.為了包裝 CLR 中程式碼所引用的程式碼地址都是有效的,必須跟蹤諸如 GC 物件和 COM CCW 之類的特殊應用。否則會出現 Unload 一個 Assembly 後,還有 CLR 物件或 COM 元件使用到這個 Assembly 的程式碼或資料地址,進而導致訪問異常。而為了避免這種錯誤進行的跟蹤,目前是在 AppDomain 一級進行的,如果要加入 Assembly.Unload 支援,則跟蹤的粒度必須降到 Assembly 一級,這雖然在技術上不是不能實現,但代價太大了。
2.如果支援 Assembly.Unload 則必須跟蹤每個0;Assembly 的程式碼使用到的控制代碼和對現有託管程式碼的引用。例如現在 JITer 在編譯方法時,生成程式碼都在一個統一的區域,如果要支援解除安裝 Assembly 則必須對每個 Assembly 都進行獨立編譯。此外還有一些類似的資源使用問題,如果要分離跟蹤技術上雖然可行,但代價較大,特別是在諸如 WinCE 這類資源有限的系統上問題比較明顯。
3.CLR 中支援跨 AppDomain 的 Assembly 載入優化,也就是 domain neutral 的優化,使得多個 AppDomain 可以共享一份程式碼,加快載入速度。而目前 v1.0 和 v1.1 無法處理解除安裝 domain neutral 型別程式碼。這也導致實現 Assembly.Unload 完整語義的困難性。
基於上述問題, Jason Zander 推薦使用其他的設計方法來回避對此功能的使用。如 Junfeng Zhang 在其 BLog 上介紹的 AppDomain and Shadow Copy,就是 ASP.NET 解決類似問題的方法。
在構造 AppDomain 時,通過 AppDomain.CreateDomain 方法的 AppDomainSetup 引數中 AppDomainSetup.ShadowCopyFiles 設定為 "true" 啟用 ShadowCopy 策略;然後設定 AppDomainSetup.ShadowCopyDirectories 為複製目標目錄;設定 AppDomainSetup.CachePath + AppDomainSetup.ApplicationName 指定快取路徑和檔名。
通過這種方法可以模擬 Assembly.Unload 的語義。實現上是將需要管理的 Assembly 載入到一個動態建立的 AppDomain 中,然後通過跨 AppDomain 的透明代理呼叫其功能,使用 AppDomain.Unload 實現 Assembly.Unload 語義的模擬。chornbe 給出了一個簡單的包裝類,具體程式碼見文章末尾。
這樣做雖然在語義上能夠基本上模擬,但存在很多問題和代價:
1.效能:在 CLR 中,AppDomain 是類似程序的邏輯概念,跨 AppDomain 通訊就跟以前跨程序通訊一樣受到諸多限制。雖然通過透明代理物件能夠實現類似跨程序 COM 物件呼叫的功能,自動完成引數的 Marshaling 操作,但必須付出相當的代價。Dejan Jelovic給出的例子(Cross-AppDomain Calls are Extremely Slow)中,P4 1.7G 下只使用內建型別的呼叫大概需要 1ms。這對於某些需要被頻繁呼叫的函式來說代價實在太大了。如他提到實現一個繪圖的外掛,在 OnPaint 裡面畫 200 個點需要 200ms 的呼叫代價。雖然可以通過批量呼叫進行優化,但跨 AppDomain 呼叫效率的懲罰是肯定無法逃脫的。好在據說 Whidbey 中,對跨 AppDomain 呼叫中的內建型別,可以做不 Marshal 的優化,以至於達到比現有實現呼叫速度快 7 倍以上,...,我不知道該誇獎 Whidbey 實現的好呢,還是痛罵現有版本之爛,呵呵
2.易用性:需要單獨解除安裝的 Assembly 中型別可能不支援 Marshal,此時就需要自行處理型別的管理。
3.版本:在多個 AppDomain 中如何包裝版本載入的正確性。
此外還有安全方面問題。對普通的 Assembly.Load 來說,載入的 Assembly 是執行在載入者的 evidence 下,而這絕對是一個安全隱患,可能遭受類似 unix 下面通過溢位以 root 許可權讀寫檔案的程式來改寫系統檔案的類似攻擊。而單獨在一個 AppDomain 中載入 Assembly 就能夠單獨設定 CAS 許可權,降低執行許可權。因為 CLR 架構下的四級許可權控制機制,最細的粒度只能到 AppDomain。好在據說 Whidbey 會加入對使用不同 evidence 載入 Assembly 的支援。
通過這些討論可以看到,Assembly.Unload 對於基於外掛模型的程式來說,其語義的存在是很重要的。但在目前和近幾個版本來說,通過 AppDomain 來模擬其語義是比較合適的選擇,雖然要付出效能和易用性的問題,但能夠更大程度上控制功能和安全性等方面因素。長遠來說,Assembly.Unload 的實現是完全可行的, 中對類的解除安裝就是最好的例子,前面那些理由實際上都是工作量和複雜度方面的問題,並不存在無法解決的技術問題。
# re: AppDomain and Shadow Copy 4/30/2004 2:34 AM chornbe
You must also encapsulate the loaded assembly into another class,which is loaded by the new appdomain. Here's the code as it's working for me: (I've created a few custom exception types,and you'll nothttp://www.cppcns.comice I had them back - they're not descended from MarshalByRefObject so I can't just throw them from the encapsulated code)
--- cut first class file
using System;
using System.Reflection;
using System.Collections;
namespace Loader{
/* contains assembly loader objects,stored in a hash
* and keyed on the .dll file they represent. Each assembly loader
* object can be referenced by the original name/path and is used to
* load objects,returned as type Object. It is up to the calling class
* to cast the object to the necessary type for consumption.
* External interfaces are highly recommended!!
* */
public class ObjectLoader : IDisposable {
// essentially creates a parallel-hash pair setup
// one appDomain per loader
protected Hashtable domains = new Hashtable();
// one loader per assembly DLL
protected Hashtable loaders = new Hashtable();
public ObjectLoader() {/*...*/}
public object GetObject( string dllName,string typeName,object[] constructorParms ){
Loader.AssemblyLoader al = null;
object o = null;
try{
al = (Loader.AssemblyLoader)loaders[ dllName ];
} catch (Exception){}
if( al == null ){
AppDomainSetup setup = new AppDomainSetup();
setup.ShadowCopyFiles = "true";
AppDomain domain = AppDomain.CreateDomain( dllName,null,setup );
domains.Add( dllName,domain );
object[] parms = { dllName };
// object[] parms = null;
BindingFlags bindings = BindingFlags.CreateInstance | BindingFlags.Instance | BindingFlags.Public;
try{
al = (Loader.AssemblyLoader)domain.CreateInstanceFromAndUnwrap(
"Loader.dll","Loader.AssemblyLoader",true,bindings,parms,null
);
} catch (Exception){
throw new AssemblyLoadFailureException();
}
if( al != null ){
if( !loaders.ContainsKey( dllName ) ){
loaders.Add( dllName,al );
} else {
throw new AssemblyAlreadyLoadedException();
}
} else {
throw new AssemblyNotLoadedException();
}
}
if( al != null ){
o = al.GetObject( typeName,constructorParms );
if( o != null && o is AssemblyNotLoadedException ){
throw new AssemblyNotLoadedException();
}
if( o == null || o is ObjectLoadFailureException ){
string msg = "Object could not be loaded. Check that type name " + typeName +
" and constructor parameters are correct. Ensure that type name " + typeName +
" exists in the assembly " + dllName + ".";
throw new ObjectLoadFailureException( msg );
}
}
return o;
}
public void Unload( string dllName ){
if( domains.ContainsKey( dllName ) ){
AppDomain domain = (AppDomain)domains[www.cppcns.com dllName ];
AppDomain.Unload( domain );
domains.Remove( dllName );
}
}
~ObjectLoader(){
dispose( false );
}
public void Dispose(){
dispose( true );
}
private void dispose( bool disposing ){
if( disposing ){
loaders.Clear();
foreach( object o in domains.Keys ){
string dllName = o.ToString();
Unload( dllName );
}
domains.Clear();
}
}
}
}
--- end cut
--- cut second class file
using System;
using System.Reflection;
namespace Loader {
// container for assembly and exposes a GetObject function
// to create a late-bound object for casting by thwww.cppcns.come consumer
// this class is meant to be contained in a separate appDomain
// controlled by ObjectLoader class to allow for proper encapsulation
// which enables proper shadow-copying functionality.
internal class AssemblyLoader : MarshalByRefObject,IDisposable {
#region class-level declarations
private Assembly a = null;
#endregion
#region constructors and destructors
public AssemblyLoader( string fullPath ){
if( a == null ){
a = Assembly.LoadFrom( fullPath );
}
}
~AssemblyLoader(){
dispose( false );
}
public void Dispose(){
dispose( true );
}
private void dispose( bool disposing ){
if( disposing ){
a = null;
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
System.GC.Collect( 0 );
}
}
#endregion
#region public functionality
public object GetObject( string typename,object[] ctorParms ){
BindingFlags flags = BindingFlags.CreateInstance | BindingFlags.Instance | BindingFlags.Public;
object o = null;
if( a != null ){
try{
o = a.CreateInstance( typename,flags,ctorParms,null );
} catch (Exception){
o = new ObjectLoadFailureException();
}
} else {
o = new AssemblyNotLoadedException();
}
return o;
}
public object GetObject( string typename ){
return GetObject( typename,null );
}
#endregion
}
}
--- end cut
相關的一些資源:
Why isn't there an Assembly.Unload method?
http://blogs.msdn.com/jasonz/archive/2004/05/31/145105.aspx
AppDomains ("application domains")
http://blogs.msdn.com/cbrumme/archive/2003/06/01/51466.aspx
AppDomain and Shadow Copy
http://blogs.msdn.com/junfeng/archive/2004/02/09/69919.aspx
到此這篇關於C# 有關Assembly.Unload詳解的文章就介紹到這了,更多相關C# 有關Assembly.Unload內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!
