2018年1月29日 星期一

[.net] 動態加卸載 dll 檔

一開始動態加載,我就想到我另一個專案使用的 Activator.CreateInstanceFrom 函數來產生個體,本來以為把變數釋放掉後 dll 就會解除鎖定,但發現不行,原來 AppDomain 有快取的制度,所以當第二次透過那函數引用該 dll 時,會直接從 cache 拉回來,這顯然不是我所想要的,而且 dll 也會一直被鎖定住

所以我就去找其它方法,透過 Assembly 來載入,還是一樣,然後就在網路上找到有人提供一個解決方案了
1byte[] dllBytes = File.ReadAllBytes("MyDll.dll"); 
2Assembly assembly = Assembly.Load(dllBytes); 
3MyClass instance = (MyClass)assembly.CreateInstance("MyDll.MyClass"); 
開新視窗(view plain) | 列印(print) | ?
透過這樣的方法是實現動態載入了,而且也不會對實體檔案上鎖,但是我前面有提到過在 AppDomain 中有快取機制,所以這些載進來的其實一直都在記憶體裡,直到 AppDomain 結束,這樣的方法是能解決問題但不理想,因為我的服務可能重開電腦他才會被關閉再重開一次,也就是 AppDomain 要到那時才釋放,這樣記憶體也不知會吃掉多少,我連 GC 會不會回收已經沒指標指到的那些 dll 實體會不會回收都不確定了!

所以就繼續找了下一個方法,既然是因為 AppDomain 的快取機制,那我就再創一個 AppDomain 就行了吧,事實證明這樣的確是成功解決了,因為 AppDomain 才能作卸載的動作(Assembly 是不提供卸載的)

Common:這裡面放著通用函數及介面,其中在裡面有定義一個 IReport 就是報表的 Interface
Report:這裡面放著報表用的類別庫,當然他會參考 Common ,並實作 IReport
WebApp:這裡則是放著 ASP.Net 網頁,線上報表頁面都在裡面,其中他會同時參考 Common 及 Report,因為 WebApps 沒有鎖定 dll 的問題,所以就算不用動態加載對編輯環境而言也不會有差異,所以並不需要特別讓他去動態加載報表的類別
WinService:這裡放著我撰寫好的服務,基本上一開始,連登入桌面也不用,他就自動開始啟動了,每十分鐘就會查詢一次資料庫,如果有需要產出報表才會去呼叫相對應的報表檔產生函數並將產出的報表寄送出去,其中他會參考 Common 類別
其中因為 Service 是十分鐘才輪一次,在這十分鐘的空檔他不應該對任何編譯造成阻礙,畢竟那十分鐘他在睡覺,而實事是不透過新建 AppDomain 的方式,他就是會鎖定原始的 dll 檔,所以才需要提出這樣的動態加卸載機制

1using System; 
2using System.Collections.Generic; 
3using System.ComponentModel; 
4using System.Data; 
5using System.Diagnostics; 
6using System.Linq; 
7using System.ServiceProcess; 
8using System.Text; 
9using System.Reflection; 
10using System.Configuration; 
11using System.Security.Policy; 
12using System.Runtime.Remoting; 
13using System.IO; 
14
15namespace WinServices 
16
17    public class DynamicLoadReportDLL : IDisposable 
18    { 
19        private AppDomain appDomain; 
20        private AppDomainSetup appDomainSetup; 
21        private string assemblyFile; 
22        private Evidence evidence; 
23
24        public void Load(string AssemblyFile) 
25        { 
26            assemblyFile = AssemblyFile; 
27            if (appDomainSetup == null
28            { 
29                string pluginDirectory = Path.GetDirectoryName(AssemblyFile); 
30                appDomainSetup = new AppDomainSetup(); 
31                appDomainSetup.ApplicationName = "RoutineReportDynamicDLL"
32                appDomainSetup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory; 
33                appDomainSetup.PrivateBinPath = pluginDirectory; 
34                appDomainSetup.CachePath = Path.Combine(pluginDirectory, "Cache/"); 
35                appDomainSetup.ShadowCopyFiles = "true"
36                appDomainSetup.ShadowCopyDirectories = pluginDirectory; 
37                Evidence baseEvidence = AppDomain.CurrentDomain.Evidence; 
38                evidence = new Evidence(baseEvidence); 
39                appDomain = AppDomain.CreateDomain("RoutineReportDynamicDLL_Domain", evidence, appDomainSetup); 
40            } 
41        } 
42
43        public Common.IReport GetReport(string typename, DateTime timeline) 
44        { 
45            return (Common.IReport)appDomain.CreateInstanceFromAndUnwrap(assemblyFile, typename, false, BindingFlags.Default, nullnew object[] { timeline }, nullnull, evidence); 
46        } 
47
48        public void UnLoad() 
49        { 
50            if(appDomain != null
51                AppDomain.Unload(appDomain); 
52            appDomain = null
53            GC.Collect(); 
54            GC.WaitForPendingFinalizers(); 
55            GC.Collect(0); 
56        } 
57
58        #region IDisposable 成員 
59
60        public void Dispose() 
61        { 
62            UnLoad(); 
63        } 
64
65        #endregion 
66    } 
67
開新視窗(view plain) | 列印(print) | ?
移掉沒使用到的 using ,可以用 Visual Studio 來移除

這類別主要是建構後透過 Load 函數建構一個新的 AppDomainSetup 來儲存 AppDomain 的設定
之後則透過 AppDomain.CreateDomain 函數來建構一個新的 AppDomain 給這類別庫使用

而 UnLoad 方法則將 Load 建構出來的 appDomain 卸載掉,並執行 GC 的命令回收記憶體資源

GetReport 則是真正要呼叫的方法,讓他建立一個 Report 類別(類別的名稱是 typename 控制)的實體,而建構子的參數則是 timeline

透過這樣的方式,服務每十分鐘到時,他只要呼叫這類別的 Load 語法,就能建構出要動態參考的 dll 的 appDomain 了

而在 GetReport 時實作 dll 的類別並回傳給服務,服務只要呼叫他後就能執行指定的類別產生相關的報表了

最後只要透過 UnLoad 中釋放 appDomain ,就可以釋放 dll ,讓他不被鎖定也不會造成記憶體的負擔

ref:
http://mywct.pixnet.net/blog/post/38622519-%E5%8B%95%E6%85%8B%E5%8A%A0%E5%8D%B8%E8%BC%89-dll-%E6%AA%94

http://www.cnblogs.com/tianma3798/p/7497272.html

http://dudu.cnblogs.com/archive/2004/03/04/2182.html









































沒有留言:

張貼留言