AppDomain 表示應用程序域,它是一個應用程序在其中執行的獨立環境。每個應用程序只有一個主應用程序域,但是一個應用程序可以創建多個子應用程序域。
因此可以通過 AppDomain 創建新的應用程序域,在新創建的子應用程序域中加載執行程序集並且在執行完畢後釋放程序集資源,來實現系統在運行狀態下,程序集的動態加載或卸載,從而達到系統運行中程序集熱更新的目的。
以下為整個原理的實現代碼
主應用程序入口:
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(); } } }
創建新的應用程序域並且在新的應用程序域中調用透明代理類:
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(); } } }
創建應用程序代理類:
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(); } } }
所有涉及到需要動態加載或釋放的資源,都需要放在代理類中進行操作,只有在此代理類中進行托管的代碼才是屬於新建的應用程序域的操作。
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; } } }
讀取配置文件信息:
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上面的文檔。