在目前的项目开发中,分布式开发已经逐渐成为主流。一个项目要是没有采用分布式架构,都很差意思跟别人说这是一个完整的项目。这句话虽然有些过激,可是随着人们对效率的要求在提升,以及产品须要提高用户体验。只有在软件项目的效率和体验作到高质量,才能够赢得用户和市场。java
对于.NET项目,咱们使用较多的分布式结构有Webservice,.Net remoting,MSMQ,WCF,WebAPI等等,咱们在使用这些框架的时候,从这些分布式框架中获得了很好的用户体验。在.NET项目中,分布式架构对项目的开发也有很大的效率提高。安全
不少人会问,这些分布式框架的底层原理是什么呢?恐怕谁也不敢轻言几句就能够描述完毕,在这个博文系列中,就是简单的描述一下这些分布式结构的底层实现原理。架构
本文主要讲解对象在应用程序域中的传递。主要讲解应用程序域的一些核心对象,对于应用程序域的操做出现的比较少,因此在这里给出的是程序集的一些基本操做。若有不足之处,还望多多指正。app
AppDomain在不少场合都是被翻译为“应用程序域”,在本文中也将采用这一翻译。对于.NET的开发者,对于CLR应该是最熟悉不过了,CLR相似于java的JVM。在CLR中,AppDomain规定了代码的执行范围,提供了错误隔离的程度,提供了一个安全隔离度,而且拥有本身的资源。AppDomain的具体功能,有以下图:框架
AppDomain相似与系统的进程,进程是有操做系统进行建立,AppDomain是由CLR进行建立。一个给定的AppDomain必须驻留在一个操做系统的进程中,而一个给定的进程能够寄宿多个AppDomain。有以下图:dom
如上图所示,一个对象正好存放在一个AppDomain种,值也同样。一个AppDomain中的对象引用必须是引用同一AppDomain中的对象,AppDomain的行为就好像拥有本身私有的地址空间。若是两个AppDomain须要使用一个类型,必须为每一个AppDomain分别初始化和分配一次类型。必须为各个用到类型的AppDomain分别加载和初始化一次类型的方法和程序集。进程种的各个AppDomain要维护类型的不一样拷贝。对于类型的静态子类,每一个AppDomain都有其本身的私有副本。分布式
AppDomain的资源有如图:ide
对于应用AppDomain的资源被加载,一直在内存中,卸载AppDomain资源是惟一卸载模块或者程序集的途径,卸载AppDomain资源也是回收类型静态字段所占内存的惟一方式。ui
在上面提到过操做系统的线程与AppDomain相似,在CLR中定义了System.Threading.Thread,在AppDomain中表示为可调度的实体,在这里提出一个新的概念,那就是“软线程”和“硬线程”,顾名思义,操做系统的线程被称为“硬线程”,CLR中的System.Threading.Thread被称为“软线程”。一个CLR软线程对象驻留在一个肯定的AppDomain中;一个给定的AppDomain可能有多个软线程对象。在当前的CLR中,对于给定的AppDomain,硬线程至多有一个软线程对象属于他,若是一个硬线程运行在多个AppDomain中,每一个AppDomain都会有一个明显的软线程对象属于该线程。当给定的硬线程进入AppDomain后,就会获得一样的软线程对象。this
上面介绍了一些AppDomain的基本概念,接下来咱们来简单了解一下AppDomain的相关操做和核心对象。在.NET种能够经过System.AppDomain类型访问AppDomain。在这里咱们具体了解一下System.AppDomain类型的方法和属性。对于该类的说明:https://msdn.microsoft.com/en-us/library/system.appdomain(v=vs.110).aspx。
(1).CurrentDomain:获取当前Thread 的当前应用程序域。
public static AppDomain CurrentDomain { get { return Thread.GetDomain(); } }
由以上代码可知,该属性为一个静态属性,而且只有一个只读属性。该属性只是简单地提取存储在硬线程的TLS(线程本地存储区)中的AppDomain引用。你能够在Thread.CurrentThread属性中,从硬线程的TLS中提取当前的软线程对象。
(2).GetData():为指定名称获取存储在当前应用程序域中的值。
[SecuritySafeCritical] public object GetData(string name) { if (name == null) throw new ArgumentNullException("name"); switch (AppDomainSetup.Locate(name)) { case -1: if (name.Equals(AppDomainSetup.LoaderOptimizationKey)) return (object) this.FusionStore.LoaderOptimization; object syncRoot = ((ICollection) this.LocalStore).SyncRoot; bool lockTaken = false; object[] objArray; try { Monitor.Enter(syncRoot, ref lockTaken); this.LocalStore.TryGetValue(name, out objArray); } finally { if (lockTaken) Monitor.Exit(syncRoot); } if (objArray == null) return (object) null; if (objArray[1] != null) ((IPermission) objArray[1]).Demand(); return objArray[0]; case 0: return (object) this.FusionStore.ApplicationBase; case 1: return (object) this.FusionStore.ConfigurationFile; case 2: return (object) this.FusionStore.DynamicBase; case 3: return (object) this.FusionStore.DeveloperPath; case 4: return (object) this.FusionStore.ApplicationName; case 5: return (object) this.FusionStore.PrivateBinPath; case 6: return (object) this.FusionStore.PrivateBinPathProbe; case 7: return (object) this.FusionStore.ShadowCopyDirectories; case 8: return (object) this.FusionStore.ShadowCopyFiles; case 9: return (object) this.FusionStore.CachePath; case 10: return (object) this.FusionStore.LicenseFile; case 11: return (object) (bool) (this.FusionStore.DisallowPublisherPolicy ? 1 : 0); case 12: return (object) (bool) (this.FusionStore.DisallowCodeDownload ? 1 : 0); case 13: return (object) (bool) (this.FusionStore.DisallowBindingRedirects ? 1 : 0); case 14: return (object) (bool) (this.FusionStore.DisallowApplicationBaseProbing ? 1 : 0); case 15: return (object) this.FusionStore.GetConfigurationBytes(); default: return (object) null; } }
每个AppDomain有本身的环境属性集,能够经过SetData和GetData方法访问,在这里给出了GetData()方法的源码。该方法接收一个string参数,预约义应用程序域属性的名称,或已定义的应用程序域属性的名称。返回一个属性的值,或 null(若是属性不存在)。AppDomainSetup类为一个封闭类,表示能够添加到System.AppDomain的实例的程序集绑定信息。
(3).CreateDomain:使用指定的名称、证据和应用程序域设置信息建立新的应用程序域。
[SecuritySafeCritical] [SecurityPermission(SecurityAction.Demand, ControlAppDomain = true)] public static AppDomain CreateDomain(string friendlyName, Evidence securityInfo, AppDomainSetup info) { return AppDomain.InternalCreateDomain(friendlyName, securityInfo, info); }
该方法存在几个重载,接收三个参数,域的友好名称。friendlyName:此友好名称可在用户界面中显示以标识域;securityInfo:肯定代码标识的证据,该代码在应用程序域中运行。传递 null 以使用当前应用程序域的证据。info:包含应用程序域初始化信息的对象。该方法返回一个新建立的应用程序域。
(4).ExecuteAssembly():使用指定的证据和实参执行指定文件中包含的程序集。
[Obsolete("Methods which use evidence to sandbox are obsolete and will be removed in a future release of the .NET Framework. Please use an overload of ExecuteAssembly which does not take an Evidence parameter. See http://go.microsoft.com/fwlink/?LinkID=155570 for more information.")] public int ExecuteAssembly(string assemblyFile, Evidence assemblySecurity, string[] args) { if (assemblySecurity != null && !this.IsLegacyCasPolicyEnabled) throw new NotSupportedException(Environment.GetResourceString("NotSupported_RequiresCasPolicyImplicit")); RuntimeAssembly assembly = (RuntimeAssembly) Assembly.LoadFrom(assemblyFile, assemblySecurity); if (args == null) args = new string[0]; return this.nExecuteAssembly(assembly, args); }
当建立一个AppDomain后,可使用一系列技术强制它加载和执行代码,能够采用ExecuteAssembly方法。该方法将目标AppDomain加载到程序集中,而且执行其主入口点。在父AppDomain种,ExecuteAssembly方法不会加载或者初始化指定的程序集。ExecuteAssembly是一个同步的例程,这就意味着调用者将被阻塞,直到程序的Main方法把控制权交还运行时。
ExecuteAssembly方法存在几个重载版本,在这里只拿出一个版原本说明。该方法接收三个参数,assemblyFile:包含要执行程序集的文件的名称;assemblySecurity:为程序集提供的证据;args:程序集的入口点的实参。该方法返回 程序集的入口点返回的值。该方法使用Assembly.LoadFrom来加载程序集。有关程序集的内容将在下一篇讲解。
(5).DoCallBack():在另外一个应用程序域中执行代码,该应用程序域由指定的委托标识。
public void DoCallBack(CrossAppDomainDelegate callBackDelegate) { if (callBackDelegate == null) throw new ArgumentNullException("callBackDelegate"); callBackDelegate(); }
这个指定方法必须是静态的,而且它的签名与CrossAppDomainDelegate签名匹配。
using System; using System.Collections.Generic; using System.IO; using System.Reflection; namespace AppDomainToolkit { /// <summary> /// 用于肯定加载器应加载哪些加载上下文程序集。 /// </summary> public enum LoadMethod { /// <summary> /// 将程序集加载到LoadFrom上下文中,这将使程序集及其全部引用被发现 ///并加载到目标应用程序域中。 尽管它对DLL地狱的倾向,这多是去的方式 /// default,只要确保将应用程序的基本目录传递给AssemblyResolver实例等 ///能够正确解析引用。 这也容许同时加载同名的多个程序集 ///维护单独的文件名。 这是推荐的方式。 /// </summary> LoadFrom, /// <summary> /// 使用原始文件名将组合件加载到内存中。 这将以匿名方式加载程序集,所以它不会有 ///一个加载上下文。 使用这个,若是你想要的位加载,但确保经过这个文件所在的目录 /// AssemblyResolver实例,以便您能够再次找到它。 这是相似于LoadFrom,除非你没有获得免费 ///经过融合查找已经存在的程序集名称。 使用它能够更好地控制汇编文件加载。 /// </summary> LoadFile, /// <summary> /// 使用原始文件名将目标程序集的位加载到内存中。 这本质上是一个动态组件 ///为全部的CLR关心。 你将永远不能找到这个与程序集解析器,因此不要使用这,除非你看 ///按名称。 当心这一个。 /// </summary> LoadBits } /// <summary> /// 这个类将会把程序集加载到它加载到的任何应用程序域中。 这只是一个简单的方便 /// wrapper环绕静态Assembly.Load *方法,主要的好处是可以加载程序集 ///匿名按位。 当您以这种方式加载程序集时,不会有任何DLL文件的锁定。 /// </summary> public class AssemblyLoader : MarshalByRefObject, IAssemblyLoader { #region Public Methods /// <inheritdoc /> /// <remarks> /// 若是此实例的LoadMethod设置为LoadBits,而且PDB文件的路径未指定,那么咱们将尝试猜想 ///到PDB的路径并加载它。 注意,若是一个程序集被加载到内存中而没有调试符号,那么 /// image将被抛出。 警戒这个。 使用LoadBits方法加载程序集不会锁定 /// DLL文件,由于整个程序集被加载到内存中而且文件句柄被关闭。 可是, ///以这种方式加载的程序集不会有与之关联的位置,所以您必须键入程序集 ///它的强名。 当将同一程序集的多个版本加载到一个程序集时,这可能会致使问题 ///应用程序域。 /// </remarks> public Assembly LoadAssembly(LoadMethod loadMethod, string assemblyPath, string pdbPath = null) { Assembly assembly = null; switch (loadMethod) { case LoadMethod.LoadFrom: assembly = Assembly.LoadFrom(assemblyPath); break; case LoadMethod.LoadFile: assembly = Assembly.LoadFile(assemblyPath); break; case LoadMethod.LoadBits: // Attempt to load the PDB bits along with the assembly to avoid image exceptions. pdbPath = string.IsNullOrEmpty(pdbPath) ? Path.ChangeExtension(assemblyPath, "pdb") : pdbPath; // Only load the PDB if it exists--we may be dealing with a release assembly. if (File.Exists(pdbPath)) { assembly = Assembly.Load( File.ReadAllBytes(assemblyPath), File.ReadAllBytes(pdbPath)); } else { assembly = Assembly.Load(File.ReadAllBytes(assemblyPath)); } break; default: // In case we upadate the enum but forget to update this logic. throw new NotSupportedException("The target load method isn't supported!"); } return assembly; } /// <inheritdoc /> /// <remarks> /// 这个实现将执行目标程序集的尽力负载,它是必需的引用 ///进入当前应用程序域。 .NET框架在咱们容许使用的调用上锁定咱们 ///当加载这些程序集时,因此咱们须要依赖于AssemblyResolver实例附加的 /// AppDomain为了加载咱们想要的方式。 /// </remarks> public IList<Assembly> LoadAssemblyWithReferences(LoadMethod loadMethod, string assemblyPath) { var list = new List<Assembly>(); var assembly = this.LoadAssembly(loadMethod, assemblyPath); list.Add(assembly); foreach (var reference in assembly.GetReferencedAssemblies()) { list.Add(Assembly.Load(reference)); } return list; } /// <inheritdoc /> /// <remarks> /// Just a simple call to AppDomain.CurrentDomain.GetAssemblies(), nothing more. /// </remarks> public Assembly[] GetAssemblies() { return AppDomain.CurrentDomain.GetAssemblies(); } #endregion } }
本文主要讲解了应用程序域的相关概念,本系列主要讲解.NET对象的跨应用程序域的传递,因为设计应用程序域的内容,因此本文主要讲解了一些基本概念,以及一些基本的对象,对于应用程序域包含的程序集的相关内容将在下面进行操做。在实际的项目中,不多直接取操做应用程序域,比较多的是直接操做程序集,因此在本文的最后给出了一个就暗淡的程序集的操做方法。