AppDomain 表示應用程序域,它是一個應用程序在其中執行的獨立環境。每一個應用程序只有一個主應用程序域,可是一個應用程序能夠建立多個子應用程序域。跨域
所以能夠經過 AppDomain 建立新的應用程序域,在新建立的子應用程序域中加載執行程序集而且在執行完畢後釋放程序集資源,來實現系統在運行狀態下,程序集的動態加載或卸載,從而達到系統運行中程序集熱更新的目的。緩存
所謂應用程序域,.Net引入的一個概念,指的是一種邊界,它標識了代碼的運行範圍,在其中產生的任何行爲,包括異常都不會影響到其餘應用程序域,起到安全隔離的效果。也能夠當作是一個輕量級的進程。安全
一個進程能夠包含多個應用程序域,各個域之間相互獨立。以下是一個.net進程的組成(圖片來自網絡)網絡
如下爲整個原理的實現代碼app
主應用程序入口:dom
using Kernel.ServiceAgent; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.Remoting; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Kernel.App { class Program { static void Main(string[] args) { Console.WriteLine(""); using (ServiceManager<IObjcet> manager = new ServiceManager<IObjcet>()) { string result = manager.Proxy.Put("apprun one"); Console.WriteLine(result); Console.WriteLine(""); Console.WriteLine(" Thread AppDomain info "); Console.WriteLine(manager.CotrProxy.FriendlyName); Console.WriteLine(Thread.GetDomain().FriendlyName); Console.WriteLine(manager.CotrProxy.BaseDirectory); Console.WriteLine(manager.CotrProxy.ShadowCopyFiles); Console.WriteLine(""); } Console.ReadLine(); } } }
建立新的應用程序域而且在新的應用程序域中調用透明代理類:ide
using Kernel.Interface; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Security.Policy; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Kernel.ServiceAgent { public class ServiceManager<T> : IDisposable where T : class { private AppDomain ctorProxy = null; /// <summary> /// 應用程序運行域容器 /// </summary> public AppDomain CotrProxy { get { return ctorProxy; } } private T proxy = default(T); public T Proxy { get { if (proxy == null) { proxy = (T)InitProxy(AssemblyPlugs); } return proxy; } } private string assemblyPlugs; /// <summary> /// 外掛插件程序集目錄路徑 /// </summary> public string AssemblyPlugs { get { assemblyPlugs = ConfigHelper.GetVaule("PrivatePath"); if (assemblyPlugs.Equals("")){ assemblyPlugs = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins"); } if (!Directory.Exists(assemblyPlugs)) { Directory.CreateDirectory(assemblyPlugs); } return assemblyPlugs; } set { assemblyPlugs = value; } } public ServiceManager() { if (proxy == null) { proxy = (T)InitProxy(AssemblyPlugs); } } private T InitProxy(string assemblyPlugs) { try { //AppDomain.CurrentDomain.SetShadowCopyFiles(); //Get and display the friendly name of the default AppDomain. //string callingDomainName = Thread.GetDomain().FriendlyName; //Get and display the full name of the EXE assembly. //string exeAssembly = Assembly.GetEntryAssembly().FullName; //Console.WriteLine(exeAssembly); AppDomainSetup ads = new AppDomainSetup(); ads.ApplicationName = "Shadow"; //應用程序根目錄 ads.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory; //子目錄(相對形式)在AppDomainSetup中加入外部程序集的所在目錄,多個目錄用分號間隔 ads.PrivateBinPath = assemblyPlugs; //設置緩存目錄 ads.CachePath = ads.ApplicationBase;
//獲取或設置指示影像複製是打開仍是關閉 ads.ShadowCopyFiles = "true";
//獲取或設置目錄的名稱,這些目錄包含要影像複製的程序集 ads.ShadowCopyDirectories = ads.ApplicationBase; ads.DisallowBindingRedirects = false; ads.DisallowCodeDownload = true; ads.ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; //Create evidence for the new application domain from evidence of Evidence adevidence = AppDomain.CurrentDomain.Evidence; // Create the second AppDomain. ctorProxy = AppDomain.CreateDomain("AD #2", adevidence, ads); //Type.GetType("Kernel.TypeLibrary.MarshalByRefType").Assembly.FullName string assemblyName = Assembly.GetExecutingAssembly().GetName().FullName; //string assemblyName = typeof(MarshalByRefType).Assembly.FullName // Create an instance of MarshalByRefObject in the second AppDomain. // A proxy to the object is returned. Console.WriteLine("CtorProxy:" + Thread.GetDomain().FriendlyName); //TransparentFactory factory = (IObjcet)ctorProxy.CreateInstance("Kernel.TypeLibrary",
"Kernel.TypeLibrary.TransparentFactory").Unwrap(); TransparentAgent factory = (TransparentAgent)ctorProxy.CreateInstanceAndUnwrap(assemblyName,
typeof(TransparentAgent).FullName); Type meetType = typeof(T); string typeName = AssemblyHelper.CategoryInfo(meetType); object[] args = new object[0]; string assemblyPath = ctorProxy.SetupInformation.PrivateBinPath; //IObjcet ilive = factory.Create(@"E:\Plug\Kernel.SimpleLibrary.dll", "Kernel.SimpleLibrary.PlugPut", args); T obj = factory.Create<T>(assemblyPath, typeName, args); return obj; } catch (System.Exception) { throw; } } /// <summary> /// 卸載應用程序域 /// </summary> public void Unload() { try { if (ctorProxy != null) { AppDomain.Unload(ctorProxy); ctorProxy = null; } } catch(Exception) { throw; } } public void Dispose() { this.Unload(); } } }
建立應用程序代理類:this
using Kernel.Interface; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace Kernel.ServiceAgent { public class TransparentAgent : MarshalByRefObject { private const BindingFlags bfi = BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance; public TransparentAgent() { } /// <summary> Factory method to create an instance of the type whose name is specified, /// using the named assembly file and the constructor that best matches the specified parameters. </summary> /// <param name="assemblyFile"> The name of a file that contains an assembly where the type named typeName is sought. </param> /// <param name="typeName"> The name of the preferred type. </param> /// <param name="constructArgs"> An array of arguments that match in number, order,
/// and type the parameters of the constructor to invoke, or null for default constructor. </param> /// <returns> The return value is the created object represented as IObjcet. </returns> public IObjcet Create(string assemblyFile, string typeName, object[] args) { return (IObjcet)Activator.CreateInstanceFrom(assemblyFile, typeName, false, bfi, null, args, null, null).Unwrap(); } public T Create<T>(string assemblyPath, string typeName, object[] args) { string assemblyFile = AssemblyHelper.LoadAssemblyFile(assemblyPath, typeName); return (T)Activator.CreateInstanceFrom(assemblyFile, typeName, false, bfi, null, args, null, null).Unwrap(); } } }
全部涉及到須要動態加載或釋放的資源,都須要放在代理類中進行操做,只有在此代理類中進行託管的代碼纔是屬於新建的應用程序域的操做。spa
using Kernel.Interface; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Kernel.ServiceAgent { public class AssemblyHelper { /// <summary> /// 獲取泛型類中指定屬性值 /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> public static string CategoryInfo(Type meetType) { object[] attrList = meetType.GetCustomAttributes(typeof(CategoryInfoAttribute), false); if (attrList != null) { CategoryInfoAttribute categoryInfo = (CategoryInfoAttribute)attrList[0]; return categoryInfo.Category; } return ""; } public static string LoadAssemblyFile(string assemblyPlugs, string typeName) { string path = string.Empty; DirectoryInfo d = new DirectoryInfo(assemblyPlugs); foreach (FileInfo file in d.GetFiles("*.dll")) { Assembly assembly = Assembly.LoadFile(file.FullName); Type type = assembly.GetType(typeName, false); if (type != null) { path = file.FullName; } } return path; } } }
讀取配置文件信息:.net
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Configuration; namespace Kernel.ServiceAgent { public class ConfigHelper { public static string GetVaule(string configName) { string configVaule = ConfigurationManager.AppSettings[configName]; if (configVaule != null && configVaule != "") { return configVaule.ToString(); } return ""; } } }
配置文件相關配置信息:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <appSettings> <add key="PrivatePath" value="E:\Plugins"/> </appSettings> </configuration>
建立接口信息:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Kernel.Interface { [CategoryInfo("Kernel.SimpleLibrary.PlugPut", "")] public interface IObjcet { void Put(); string Put(string plus); } }
建立接口自定義屬性:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Kernel.Interface { /// <summary> /// 設置接口實現類自定義標註屬性 /// </summary> /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = false)] public class CategoryInfoAttribute : Attribute { public string Category { get; set; } public string Describtion { get; set; } /// <summary> /// 設置實現類自定義標註屬性 /// </summary> /// <param name="category"></param> /// <param name="describtion"></param> public CategoryInfoAttribute(string category, string describtion) { this.Category = category; this.Describtion = describtion; } } }
建立繼承至IObjcet接口帶有具體操做的實現類:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Kernel.Interface; namespace Kernel.SimpleLibrary { [Serializable] public class PlugPut : MarshalByRefObject, IObjcet { private string plugName = "my plugName value is default!"; public string PlugName { get { return plugName; } set { plugName = value; } } public PlugPut() { } public PlugPut(string plusName) { this.PlugName = plusName; } public void Put() { Console.WriteLine("Default plug value is:" + plugName); } public string Put(string plus) { Console.WriteLine("Put plus value is:" + plus); return ("-------------------- PlugPut result info is welcome -------------------------"); } } }
繼承至IObjcet接口帶有具體操做的實現類,就是屬於須要動態替換更新的程序集,因此最好將其編譯在一個單獨的程序集中,插件目錄在配置文件中可配置,示例中放置在E:\Plugins 目錄下,示例中代碼最後將生成 Kernel.SimpleLibrary.DLL ,最後將編譯好的程序集放置在 E:\Plugins\Kernel.SimpleLibrary.DLL 路徑下,程序運行後將加載此程序集,加載完畢運行完畢後並釋放此程序集。
如下兩句較爲重要,最好設置一下,不設置的後果暫時沒有嘗試
//獲取或設置指示影像複製是打開仍是關閉
ads.ShadowCopyFiles = "true";
//獲取或設置目錄的名稱,這些目錄包含要影像複製的程序集
ads.ShadowCopyDirectories = ads.ApplicationBase;
當程序運行起來後,程序集加載以後會在設置的應用程序域緩存目錄中複製一份程序集的副本,而後運行副本中的程序集,釋放掉自己加載的程序集。以上示例中會在主程序目錄下生成一個Shadow 目錄,此目錄下包含了程序集的副本文件。
小節:
若是在另外一個AppDomain 中加載程序集,而後獲取Type,最後在主AppDomain中使用CreateInstance中的Type重載建立對象,結果會是Type所屬的程序集會被加入到當前AppDomain中,而後Type的實例會在當前AppDomain中建立。
只有繼承至 MarshalByRefObject 的透明代理類纔可以進行跨域操做。
因此須要在繼承至 MarshalByRefObject 的透明代理類中進行相關操做而後返回給主應用程序域,只有在代理類中進行的代碼操做纔是屬於新建的應用程序域。不然任何運行代理類之外的代碼都是屬於主應用程序域。
此章節只是講解了程序集動態加載或卸載熱插拔的實現方式,有關AppDomain 和 AppDomainSetup 具體信息能夠參考MSDN上面的文檔。