C# Assembly.Load案例詳解
我們?cè)谑褂肅# 語(yǔ)言的Assembly.Load 來加載托管程序集并使用反射功能時(shí),一般需要先通過Assembly.Load(), Assembly.LoadFrom() 等方法將目標(biāo)托管程序集加載到當(dāng)前應(yīng)用程序域中,然后生成對(duì)應(yīng)實(shí)例,最后再進(jìn)行調(diào)用實(shí)例的屬性或者方法。
一般情況下,我們調(diào)用Assembly.Load 一類方法是不會(huì)出問題的,但是對(duì)于以下幾種情況Assembly.Load 方法無法處理:
- 程序集可能是延遲簽名的。
- 程序集可能被CAS 策略保護(hù)。
- 宿主程序與目標(biāo)程序集的處理器架構(gòu)不同。
- 當(dāng)加載目標(biāo)程序集時(shí),目標(biāo)程序集中的方法可能正在運(yùn)行。 (比如,模塊初始化)
- 程序集可能應(yīng)用了綁定策略, 你可能不會(huì)得到你想要的那個(gè)程序集。
我們現(xiàn)在關(guān)注第四種情況,因?yàn)檫@種情況是最常見的。我們思考以下幾個(gè)問題:
1. 為什么目標(biāo)程序集的方法在運(yùn)行時(shí)不允許再加載一次?
準(zhǔn)確地說是為什么在一個(gè)應(yīng)用程序域(AppDomain)中加載后的程序集默認(rèn)不允許再另外一個(gè)應(yīng)用程序域中加載?這是因?yàn)樵诘谝淮渭虞d應(yīng)用程序集時(shí),Assemlby.Load 方法會(huì)將此程序集鎖住,以防止在自己使用過程中應(yīng)用程序集被其他應(yīng)用程序修改(一般指刪除)。這其實(shí)與Win32 API 中的CreateFile 函數(shù)行為類似,我們都知道,在 Windows 中去占用一個(gè)文件最直接、最簡(jiǎn)單的方式就是調(diào)用 CreateFile API 函數(shù)來打開文件。具體請(qǐng)參照:
http://www.dbjr.com.cn/article/221122.htm
2. Assembly.Load 方法能否實(shí)現(xiàn)在加載目標(biāo)程序集時(shí)不鎖定它?
我們可以使用如下代碼加載我們的程序集:
byte[] buffer = System.IO.File.ReadAllBytes(yourFullfileNamePath); //Load assembly using byte array Assembly assembly = Assembly.Load(buffer);
后臺(tái)的實(shí)現(xiàn)是暫時(shí)先將目標(biāo)程序集鎖定,然后把程序集內(nèi)容復(fù)制到內(nèi)存中,讀取后將程序集解鎖并從內(nèi)存中加載目標(biāo)程序集的拷貝。
如果你需要通過上面的方法加載GAC 中的程序集,那么可以通過如下代碼實(shí)現(xiàn):
string assemblyName = "AssemblyTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=fffb45e56dd478e3"; Assembly ass = Assembly.ReflectionOnlyLoad(assemblyName); byte[] buffer = System.IO.File.ReadAllBytes(ass.Location); Assembly assembly = Assembly.Load(buffer);
3. Assembly.Load 方法的緩存
在我們使用Assembly.Load 系列方法加載目標(biāo)程序集時(shí),可能有各種情況導(dǎo)致加載失敗,最常見的是目標(biāo)程序集不存在而導(dǎo)致加載失敗問題。失敗后我們可能想要再加載一次或者加載多次直到成功為止,但是在.NET Framework 2.0 以后默認(rèn)是無法實(shí)現(xiàn)的,原因在于.NET Framework 2.0 以后 Assembly.Load 方法有緩存,第一次加載目標(biāo)程序集的失敗或者成功的狀態(tài)都會(huì)被緩存,這樣在你下一次加載目標(biāo)程序集時(shí)不會(huì)真的加載,會(huì)直接從緩存里取目標(biāo)程序集的內(nèi)容和狀態(tài)。
對(duì)這種情況我們可以在調(diào)用Assembly.Load 方法的宿主程序的app.config 中加入如下配置信息來禁用緩存:
<?xml version="1.0"?> <configuration> <runtime> <disableCachingBindingFailures enabled="1" /> </runtime> <startup> <supportedRuntime version="v2.0.50727"/> </startup> </configuration>
4. 還有其他方案嗎?
CLR 框架略圖:
因?yàn)槟壳癆ssembly 的加載與卸載是完全由應(yīng)用程序域控制的,一個(gè)程序集只可以通過應(yīng)用程序域加載,只能通過應(yīng)用程序域的卸載而卸載,其他任何方式都不可以!??!
那么我們可以在需要時(shí)將目標(biāo)程序集加載進(jìn)應(yīng)用程序域,不需要時(shí)將應(yīng)用程序域卸載,但是當(dāng)前應(yīng)用程序域的卸載只能通過關(guān)閉程序來實(shí)現(xiàn)。
到目前為止,看似無解了,但是我們忽略了一個(gè)事實(shí),那就是我們可以在當(dāng)前應(yīng)用程序域中創(chuàng)建子應(yīng)用程序域??梢酝ㄟ^在子應(yīng)用程序域中進(jìn)行程序集的加載,或者說只要涉及程序集加載的全部放在子應(yīng)用程序域中,主應(yīng)用程序域中不做任何與程序集加載有關(guān)的事情。
這部分內(nèi)容我就不詳細(xì)介紹了,大家可以參考:
http://www.codeproject.com/Articles/42312/Loading-Assemblies-in-Separate-Directories-Into-a
5. 我們確實(shí)需要使用Assembly.Load嗎?
因?yàn)锳ssembly.Load 是將整個(gè)程序集以及其相關(guān)的依賴程序集全部加載進(jìn)來,只要有一個(gè)出錯(cuò)就會(huì)導(dǎo)致加載失敗。如果我們只是為了使用當(dāng)前程序集的類型,而不是使用其方法或者屬性的話就完全可以拋棄Assembly.Load 方法。
微軟在.Net Framework 2.0 時(shí)介紹了幾個(gè)新的程序集加載APIs:
Assembly.ReflectionOnlyLoadFrom(String assemblyFile)
Assembly.ReflectionOnlyLoad(byte[] rawAssembly)
Assembly.ReflectionOnlyLoad(String assemblyName)
基于開篇提到的Assembly.Load 方法的5種問題, Reflection Only程序集加載APIs 可以:
- 跳過程序集強(qiáng)命名認(rèn)證。
- 跳過程序集CAS策略認(rèn)證。
- 跳過處理器架構(gòu)檢查規(guī)則。
- 不在目標(biāo)程序中執(zhí)行任何方法,包括構(gòu)造函數(shù)。
- 不應(yīng)用任何綁定策略。
使用時(shí)有如下幾個(gè)注意事項(xiàng):
1) CLR 不會(huì)搜索目標(biāo)程序集所依賴的程序集,我們必須通過ReflectionOnlyAssemblyResolve(Assembly.Load 中的對(duì)應(yīng)事件是AssemblyResolve)事件手動(dòng)處理。
2) 不可以在通過ReflectionOnly 方法加載進(jìn)來的程序集執(zhí)行任何方法,包括構(gòu)造函數(shù),只可以獲取程序集的信息和類型。
3) 建議使用Assembly.ReflectionOnlyLoadFrom 方法,但是如果目標(biāo)程序集在GAC中那么可以使用Assembly.ReflectionOnlyLoad方法。
具體示例代碼如下:
using System; using System.IO; using System.Reflection; public class ReflectionOnlyLoadTest { private String m_rootAssembly; public ReflectionOnlyLoadTest(String rootAssembly) { m_rootAssembly = rootAssembly; } public static void Main(String[] args) { if (args.Length != 1) { Console.WriteLine("Usage: Test assemblyPath"); return; } try { ReflectionOnlyLoadTest rolt = new ReflectionOnlyLoadTest(args[0]); rolt.Run(); } catch (Exception e) { Console.WriteLine("Exception: {0}!!!", e.Message); } } internal void Run() { AppDomain curDomain = AppDomain.CurrentDomain; curDomain.ReflectionOnlyAssemblyResolve += new ResolveEventHandler(MyReflectionOnlyResolveEventHandler); Assembly asm = Assembly.ReflectionOnlyLoadFrom(m_rootAssembly); // force loading all the dependencies Type[] types = asm.GetTypes(); // show reflection only assemblies in current appdomain Console.WriteLine("------------- Inspection Context --------------"); foreach (Assembly a in curDomain.ReflectionOnlyGetAssemblies()) { Console.WriteLine("Assembly Location: {0}", a.Location); Console.WriteLine("Assembly Name: {0}", a.FullName); Console.WriteLine(); } } private Assembly MyReflectionOnlyResolveEventHandler(object sender, ResolveEventArgs args) { AssemblyName name = new AssemblyName(args.Name); String asmToCheck = Path.GetDirectoryName(m_rootAssembly) + "\\" + name.Name + ".dll"; if (File.Exists(asmToCheck)) { return Assembly.ReflectionOnlyLoadFrom(asmToCheck); } return Assembly.ReflectionOnlyLoad(args.Name); } }
6. 為什么沒有Assembly.UnLoad 方法?
以下是CLR 產(chǎn)品單元經(jīng)理(Unit Manager) Jason Zander 文章中的內(nèi)容的整理:
1) 為了保證 CLR 中代碼所引用的代碼地址都是有效的,必須跟蹤諸如 GC 對(duì)象和 COM CCW 之類的特殊應(yīng)用。否則會(huì)出現(xiàn) Unload 一個(gè) Assembly 后,還有 CLR 對(duì)象或 COM 組件使用到這個(gè) Assembly 的代碼或數(shù)據(jù)地址,進(jìn)而導(dǎo)致訪問異常。為了避免這種錯(cuò)誤進(jìn)行的跟蹤,目前是在 AppDomain 一級(jí)進(jìn)行的,如果要加入 Assembly.Unload 支持,則跟蹤的粒度必須降到 Assembly 一級(jí),這雖然在技術(shù)上不是不能實(shí)現(xiàn),但代價(jià)太大了。 2) 如果支持 Assembly.Unload 則必須跟蹤每個(gè) Assembly 的代碼使用到的句柄和對(duì)現(xiàn)有托管代碼的引用。例如現(xiàn)在 JITer 在編譯方法時(shí),生成代碼都在一個(gè)統(tǒng)一的區(qū)域,如果要支持卸載 Assembly 則必須對(duì)每個(gè) Assembly 都進(jìn)行獨(dú)立編譯。此外還有一些類似的資源使用問題,如果要分離跟蹤技術(shù)上雖然可行,但代價(jià)較大,特別是在諸如 WinCE 這類資源有限的系統(tǒng)上問題比較明顯。 3) CLR 中支持跨 AppDomain 的 Assembly 載入優(yōu)化,也就是 domain neutral 的優(yōu)化,使得多個(gè) AppDomain 可以共享一份代碼,加快載入速度。而目前 v1.0 和 v1.1 無法處理卸載 domain neutral 類型代碼。這也導(dǎo)致實(shí)現(xiàn) Assembly.Unload 完整語(yǔ)義的困難性。
詳細(xì)請(qǐng)參考: http://www.dbjr.com.cn/article/221129.htm
http://blogs.msdn.com/b/jasonz/archive/2004/05/31/145105.aspx
7. 需要牢記的經(jīng)驗(yàn)
1) 只加載自己需要直接調(diào)用的程序集,不加載目標(biāo)程序集內(nèi)部引用的程序集和其他無關(guān)程序集。
2) 能使用RefelectionLoad 方法加載的程序集絕不要使用Assembly.Load 方法加載。
3) 一旦出現(xiàn)加載錯(cuò)誤,不要顯而易見認(rèn)為是程序集不存在!要檢查程序集加載緩存、是否出現(xiàn)同一程序集被不同應(yīng)用程序域加載情況等。
至此,我們已經(jīng)闡述了Assembly.Load 方法的一些特性,你已經(jīng)了解它了嗎?
參考鏈接:
http://msdn.microsoft.com/en-us/library/t07a3dye(v=vs.71).aspx
http://blogs.msdn.com/b/junfeng/archive/2004/11/03/252033.aspx
http://blogs.msdn.com/b/junfeng/archive/2004/08/24/219691.aspx
http://www.sosuo8.com/article/show.asp?id=2979
http://msdn.microsoft.com/en-us/library/ms404279.aspx
http://blog.csdn.net/xt_xiaotian/article/details/6362450
http://blogs.msdn.com/b/jasonz/archive/2004/05/31/145105.aspx
http://www.cnblogs.com/ccBoy/archive/2004/07/13/23636.html
http://www.cnblogs.com/wayfarer/archive/2004/09/29/47896.html
http://www.codeproject.com/Articles/42312/Loading-Assemblies-in-Separate-Directories-Into-a
http://msdn.microsoft.com/en-us/library/ms404312.aspx
到此這篇關(guān)于C# Assembly.Load案例詳解的文章就介紹到這了,更多相關(guān)C# Assembly.Load內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#使用RestSharp實(shí)現(xiàn)封裝常用的http請(qǐng)求方法
這篇文章主要為大家詳細(xì)介紹了C#如何使用RestSharp實(shí)現(xiàn)封裝常用的http請(qǐng)求方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2024-02-02C#如何實(shí)現(xiàn)dataGridView動(dòng)態(tài)綁定數(shù)據(jù)
這篇文章主要介紹了C#如何實(shí)現(xiàn)dataGridView動(dòng)態(tài)綁定數(shù)據(jù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04Unity實(shí)現(xiàn)簡(jiǎn)單虛擬搖桿
這篇文章主要為大家詳細(xì)介紹了Unity實(shí)現(xiàn)簡(jiǎn)單虛擬搖桿,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04