在開篇之前我想鄙視我自己一下,這個東西根本不需要去寫,本來已經有東西去實現了,正如我組長說我的,看的開源項目太少了。其實這個東西完全可以用ILMerge來解決。
然後再說說前言,開發東西久了,總會積累到一定量的Helper或Util,於是都放到一個項目裡面一起編一個dll,用的時候就方便,可是問題來了,像SQLite這種Helper需要帶上它的dll,再多封裝幾個類,附帶的dll就更多了,有時候想單單用一個很簡單的Helper,結果還帶了一大堆不相干的dll,會不爽,而且有種感覺是引用時就單純一個Common.dll就夠了,什麼System.Data.SQLite.dll,System.Data.MySql.dll我都不想帶,在一次使用內嵌資源時給流我靈感,把這堆dll在編譯的時候都放到項目資源中,需要的時候就去加載,這樣就行了。
單純這個就用到了內嵌資源使用方面的知識,另外一個就是AppDomain對dll加載方面的知識。
在使用內嵌資源時,要把資源包含在項目裡面,內嵌的資源在屬性頁面上"生成操作"選擇"潛入資源",
在編碼時要把資源用上,得用流來讀取,通過Assembly的GetManifestResourceStream(string name);方法就可以把資源的流獲取到,流到了就愛干嘛干嘛,代碼如下
1 Stream s = null; 2 byte[] dllDatas = null; 3 try 4 { 5 s = Assembly.GetExecutingAssembly().GetManifestResourceStream("ConsoleApplication2.ClassLibrary1.dll"); 6 dllDatas = new byte[s.Length]; 7 s.Read(dllDatas, 0, dllDatas.Length); 8 } 9 catch (Exception ex) 10 { 11 return null; 12 } 13 finally 14 { 15 if (s != null) 16 { 17 s.Close(); 18 s.Dispose(); 19 } 20 }
dll的流拿到流,使用它的地方就在AppDomain的AssemblyResolve事件,當應用程序域加載外部的程序集時,它會默認往兩個地方找,第一是往.NET Framework的目錄中找,再到應用程序所在目錄中找,所以在默認情況下我們開發的類庫都會跟引用程序放到一個目錄,而經常用到的System.dll才不需要放到引用程序目錄中。那假如需要的dll沒辦法從這兩個地方找到的話,程序會拋出FileNotFoundException異常,這個仍然有辦法解決的,其實在拋出FileNotFoundException異常之前,AppDomain會先觸發AssemblyResolve事件,這個事件會返回這個無法找到的程序集,返回的程序集為空,才會拋FileNotFoundException異常,只要我們注冊了AssemblyResolve事件,在綁定的方法中把dll從資源中取出來,加載上程序集之後,無法在文件目錄中找到的程序集就可以加載到程序域中,程序集中的類可以照常使用
代碼如下
1 AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve); 2 3 4 static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) 5 { 6 7 Stream s = null; 8 byte[] dllDatas = null; 9 try 10 { 11 s = Assembly.GetExecutingAssembly().GetManifestResourceStream("ConsoleApplication2.ClassLibrary1.dll"); 12 dllDatas = new byte[s.Length]; 13 s.Read(dllDatas, 0, dllDatas.Length); 14 } 15 catch (Exception ex) 16 { 17 return null; 18 } 19 finally 20 { 21 if (s != null) 22 { 23 s.Close(); 24 s.Dispose(); 25 } 26 } 27 Assembly assembly= AppDomain.CurrentDomain.Load(dllDatas); 28 return assembly; 29 }
但是存在一個問題,這個AssemblyResolve事件在封裝的Common項目中注冊比較合適,可是AssemblyResolve事件應該是主動調用的,而Common裡面的類全都是被調用的,個人覺得這個AssemlbyResolve事件可以放到Helper類的靜態構造函數裡面,此外也暫時想不出更好辦法,或者這種方式本身不是一個好的方式。
後續在開發中也發現一個問題,假如這個Common.dll在別的程序集也是通過AssemblyResolve事件被加載到程序域中的時候,這些在Common.dll裡面通過AssemlbyResolve事件加載進來的程序集有問題。此問題還暫時無法解釋,估計要學習一下CLR方面的知識。