解析.NET对象的跨应用程序域访问(上篇)
在目前的项目开发中,分布式开发已经逐渐成为主流。一个项目要是没有采用分布式架构,都不好意思跟别人说这是一个完整的项目。这句话虽然有些过激,但是随着人们对效率的要求在提高,以及产品需要提升用户体验。只有在软件项目的效率和体验做到高质量,才可以赢得用户和市场。
对于.NET项目,我们使用较多的分布式结构有Webservice,.Net remoting,MSMQ,WCF,WebAPI等等,我们在使用这些框架的时候,从这些分布式框架中得到了很好的用户体验。在.NET项目中,分布式架构对项目的开发也有很大的效率提升。
很多人会问,这些分布式框架的底层原理是什么呢?恐怕谁也不敢轻言几句就可以描述完毕,在这个博文系列中,就是简单的描述一下这些分布式结构的底层实现原理。
本文主要讲解对象在应用程序域中的传递。主要讲解应用程序域的一些核心对象,对于应用程序域的操作出现的比较少,所以在这里给出的是程序集的一些基本操作。如有不足之处,还望多多指正。
一.AppDomain解析:
AppDomain在很多场合都是被翻译为“应用程序域”,在本文中也将采用这一翻译。对于.NET的开发者,对于CLR应该是最熟悉不过了,CLR类似于java的JVM。在CLR中,AppDomain规定了代码的执行范围,提供了错误隔离的程度,提供了一个安全隔离度,并且拥有自己的资源。AppDomain的具体功能,有如下图:
1.AppDomain概述:
AppDomain类似与系统的进程,进程是有操作系统进行创建,AppDomain是由CLR进行创建。一个给定的AppDomain必须驻留在一个操作系统的进程中,而一个给定的进程可以寄宿多个AppDomain。有如下图:
如上图所示,一个对象正好存放在一个AppDomain种,值也一样。一个AppDomain中的对象引用必须是引用同一AppDomain中的对象,AppDomain的行为就好像拥有自己私有的地址空间。如果两个AppDomain需要使用一个类型,必须为每个AppDomain分别初始化和分配一次类型。必须为各个用到类型的AppDomain分别加载和初始化一次类型的方法和程序集。进程种的各个AppDomain要维护类型的不同拷贝。对于类型的静态子类,每个AppDomain都有其自己的私有副本。
AppDomain的资源有如图:
对于应用AppDomain的资源被加载,一直在内存中,卸载AppDomain资源是唯一卸载模块或者程序集的途径,卸载AppDomain资源也是回收类型静态字段所占内存的唯一方式。
在上面提到过操作系统的线程与AppDomain类似,在CLR中定义了System.Threading.Thread,在AppDomain中表示为可调度的实体,在这里提出一个新的概念,那就是“软线程”和“硬线程”,顾名思义,操作系统的线程被称为“硬线程”,CLR中的System.Threading.Thread被称为“软线程”。一个CLR软线程对象驻留在一个确定的AppDomain中;一个给定的AppDomain可能有多个软线程对象。在当前的CLR中,对于给定的AppDomain,硬线程至多有一个软线程对象属于他,如果一个硬线程运行在多个AppDomain中,每个AppDomain都会有一个明显的软线程对象属于该线程。当给定的硬线程进入AppDomain后,就会得到同样的软线程对象。
2.AppDomain核心对象解析:
上面介绍了一些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 -:
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[] != null)
((IPermission) objArray[]).Demand();
return objArray[];
case :
return (object) this.FusionStore.ApplicationBase;
case :
return (object) this.FusionStore.ConfigurationFile;
case :
return (object) this.FusionStore.DynamicBase;
case :
return (object) this.FusionStore.DeveloperPath;
case :
return (object) this.FusionStore.ApplicationName;
case :
return (object) this.FusionStore.PrivateBinPath;
case :
return (object) this.FusionStore.PrivateBinPathProbe;
case :
return (object) this.FusionStore.ShadowCopyDirectories;
case :
return (object) this.FusionStore.ShadowCopyFiles;
case :
return (object) this.FusionStore.CachePath;
case :
return (object) this.FusionStore.LicenseFile;
case :
return (object) (bool) (this.FusionStore.DisallowPublisherPolicy ? : );
case :
return (object) (bool) (this.FusionStore.DisallowCodeDownload ? : );
case :
return (object) (bool) (this.FusionStore.DisallowBindingRedirects ? : );
case :
return (object) (bool) (this.FusionStore.DisallowApplicationBaseProbing ? : );
case :
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[];
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对象的跨应用程序域的传递,由于设计应用程序域的内容,所以本文主要讲解了一些基本概念,以及一些基本的对象,对于应用程序域包含的程序集的相关内容将在下面进行操作。在实际的项目中,很少直接取操作应用程序域,比较多的是直接操作程序集,所以在本文的最后给出了一个就暗淡的程序集的操作方法。
解析.NET对象的跨应用程序域访问(上篇)的更多相关文章
- 解析.NET对象的跨应用程序域访问(下篇)
转眼就到了元宵节,匆匆忙忙的脚步是我们在为生活奋斗的写照,新的一年,我们应该努力让自己有不一样的生活和追求.生命不息,奋斗不止.在上篇博文中主要介绍了.NET的AppDomain的相关信息,在本篇博文 ...
- 解析.NET对象的跨应用程序域访问--AppDomain(上篇)
在目前的项目开发中,分布式开发已经逐渐成为主流.一个项目要是没有采用分布式架构,都不好意思跟别人说这是一个完整的项目.这句话虽然有些过激,但是随着人们对效率的要求在提高,以及产品需要提升用户体验.只有 ...
- NET对象的跨应用程序域
NET对象的跨应用程序域 转眼就到了元宵节,匆匆忙忙的脚步是我们在为生活奋斗的写照,新的一年,我们应该努力让自己有不一样的生活和追求.生命不息,奋斗不止.在上篇博文中主要介绍了.NET的AppDoma ...
- WPF 中那些可跨线程访问的 DispatcherObject(WPF Free Threaded Dispatcher Object)
原文 WPF 中那些可跨线程访问的 DispatcherObject(WPF Free Threaded Dispatcher Object) 众所周知的,WPF 中多数对象都继承自 Dispatch ...
- 对象存储 COS 帮您轻松搞定跨域访问需求
背景 早期为了避免 CSRF(跨站请求伪造) 攻击,浏览器引入了 "同源策略" 机制.如果两个 URL 的协议,主机名(域名/IP),端口号一致,则视为这两个 URL " ...
- .NET跨AppDomain访问对象
什么是AppDomain? 我们都知道windows进程,它起到应用程序隔离的作用,带来的好处是,当某个进程发生错误的时候,不会影响其他的进程,系统也不会受到影响.但是,创建windows进程的代价是 ...
- Chrome浏览器扩展开发系列之十五:跨域访问的XMLHttpRequest对象
XMLHttpRequest对象是W3C的标准API,用于访问服务器资源.XMLHttpRequest对象支持多种文本格式,如XML和JSON等.XMLHttpRequest对象可以通过HTTP和HT ...
- 解决Entity Framework查询匿名对象后的跨域访问的一种方式
在Entity Framework中,可以使用lambda表达式进行对数据的查询,而且可以将查询结果直接映射为对象或者对象列表,这极大的提高的开发速度,并且使数据层的数据更加方便处理和传递.但是很多时 ...
- Asp.Net SignalR 使用记录 技术回炉重造-总纲 动态类型dynamic转换为特定类型T的方案 通过对象方法获取委托_C#反射获取委托_ .net core入门-跨域访问配置
Asp.Net SignalR 使用记录 工作上遇到一个推送消息的功能的实现.本着面向百度编程的思想.网上百度了一大堆.主要的实现方式是原生的WebSocket,和SignalR,再次写一个关于A ...
随机推荐
- 独立安装CentOS7.4全记录
大学用了四年的笔记本快用废了,闲来想着用来装个centos,当个服务器也行,于是装上了CentOS6.9系统,由于最小化安装,而且在安装时没有安装wpa_supplicant包,笔记本本身网卡接口又坏 ...
- hdu1251+字典树常用模板
这里只简单给出几个常用的字典树的模板,要看具体介绍的请看:传送门 Problem Description Ignatius最近遇到一个难题,老师交给他很多单词(只有小写字母组成,不会有重复的单词出现) ...
- Flask使用记录
关于FLASK框架的使用 使用pycharm创建工程 在默认的templates中新增模板页面 在默认的app.py中定义路由并引用模板 @app.route("/add", me ...
- MySQL加入log_bin报错
MySQL中二进制日志功能默认是关闭的,查看各种开启方式后,确定在配置文件中加入如下配置来开启该功能: [root@bogon /]# more /etc/my.cnf [mysqld] datadi ...
- JavaScript 引用错误
在学习vue时 出现无法实现效果原始设置 <script src="js/lib/vue2.min.js"/><script src="js/lib/v ...
- Python并发解决方案
一.subprocess模块 call():执行命令,返回程序返回码(int) import subprocess print(subprocess.call("mspaint") ...
- (C#)日志接口请求响应时间
日志接口响应时间,记录接口请求信息,响应结果以及响应时间等.可以清楚的分析和了解接口状态. 如果一个一个地在接口下面做日志,那不是我们想要的结果.所以,我们选择做一个特性来控制接口要不要记录请求响应日 ...
- 服务管理之openssh
1. 使用 SSH 访问远程命令行 1.1 OpenSSH 简介 OpenSSH这一术语指系统中使用的Secure Shell软件的软件实施.用于在远程系统上安全运行shell.如果您在可提供ssh服 ...
- 感觉还是要学点c才牛逼
2019-04-06 $gcc -o hello hello.c //-o选项用来指定输出文件的文件名. gcc *.c -o hello //使用通配符编译当前目录下的所有c文件 $ gcc - ...
- AX_Query
static void example(Args _args) { SysQueryRun queryRun = new SysQueryRun(querystr(KTL_Sale ...