【CLR】解析AppDomain
目录结构:
- 什么是AppDomain
- 跨越AppDomain边界访问对象
- 按引用封送(Marshal-by-Reference)
- 按值封送(Marshal-by-Value)
- 卸载AppDomain
- 监视AppDomain
- AppDomainFirstChance异常通知
1.什么是AppDomain
在开始介绍AppDomain之前,需要先介绍一下应用程序和AppDomain的关联。首先,任何Windows应用程序都能寄宿在CLR中,寄宿CLR需要创建CLR COM服务器的实例(通过MSCorEE.dll文件)。CLR COM服务器实例在初始化时,会创建一个默认的AppDomain。
AppDomain就是一组程序集的容器。AppDomain是为隔离而设计的,除了默认的AppDomain。正在使用非托管COM接口方法或托管类型的宿主应用程序还可要求CLR创建其他的AppDomain。
AppDomain具有如下的一些特点:
1.一个AppDomain中的代码不能直接访问另一个AppDomain代码创建的对象。
2.AppDomain可以卸载。
下面的图片显示了一个Windows进程,运行着一个CLR COM服务器。该CLR运行着两个AppDomain。
2.跨越AppDomain边界访问对象
一个AppDomain中的代码可以和另一个AppDomain中的类型和对象通信,但只能通过良好定义的机制进行。通信的机制主要为按“引用封送(Marshal-by-Reference)”,和“按值封送(Marshal-by-Value)”,并不是所有类型都可以跨越AppDomain进行通信。接下里将接详细介绍。
2.1 按引用封送(Marshal-by-Reference)
#include "stdafx.h" #include <exception>using namespace System; using namespace System::Threading; using namespace System::Reflection; using namespace System::Runtime::Remoting; using namespace std;//该类实例可以跨AppDomain边界“按引用封送” public ref class MarshalByRefType : System::MarshalByRefObject{ public:MarshalByRefType(){//定义构造函数Console::WriteLine("{0} ctor running in {1}",GetType()->ToString(),Thread::GetDomain()->FriendlyName);} public:void SomeMethod(){//定义一个方法Console::WriteLine("Executing in "+Thread::GetDomain()->FriendlyName);} };int main(array<System::String ^> ^args) {//获取AppDomain的引用AppDomain^ adCallingThreadDomain=Thread::GetDomain();//获取这个AppDomain的友好字符串名称String^ callingDomainThread= adCallingThreadDomain->FriendlyName;Console::WriteLine("Default AppDomain's friendly name={0}",callingDomainThread);//显示AppDomain的名称//获取并显示AppDomain中包含“Main”方法的程序集String^ exeAssembly= Assembly::GetEntryAssembly()->FullName;Console::WriteLine("Main assembly={0}",exeAssembly);//包含了程序集的名称、语言文化、公钥、版本信息//按引用封送Marshal-by-Reference,进行跨AppDomain通信//新建一个AppDomain(从当前AppDomain继承安全性和配置)AppDomain^ ad2=AppDomain::CreateDomain("ad2");//将我们的程序集加载到新的AppDomain中,构建一个对象,并且把它封送回我们的AppDomain(实际得到的是一个代理的引用)MarshalByRefType^ mbrt=(MarshalByRefType^)ad2->CreateInstanceAndUnwrap(MarshalByRefType::typeid->Assembly->FullName,"MarshalByRefType");Console::WriteLine("Type = {0}",mbrt->GetType());//CLR 在类型上撒谎,实际上是MarshalByRefType的代理类型,但却返回了MarshalByRefType类型。 Console::WriteLine("Is Proxy={0}",RemotingServices::IsTransparentProxy(mbrt));//证明得到是对一个代理对象的引用//看起来像是在MarshalByRefType上调用一个方法,实则不然,//我们在代理类型上调用一个方法,代理使线程切换到拥有对象的//那个AppDomain上,并在真实的对象上调用这个方法mbrt->SomeMethod();//卸载新的AppDomain AppDomain::Unload(ad2);//mbrt 引用一个有效的代理对象;代理对象引用一个无效的AppDomaintry{mbrt->SomeMethod();//在代理对象上调用这个方法。AppDomain无效,造成抛出异常。}catch(AppDomainUnloadedException^ e){Console::WriteLine(e->Message);}Console::ReadLine();return 0; }
运行结果:
Default AppDomain's friendly name=ConsoleApplication13.exe Main assembly=ConsoleApplication13, Version=1.0.6761.1527, Culture=neutral, PublicKeyToken=null MarshalByRefType ctor running in ad2 Type = MarshalByRefType Is Proxy=True Executing in ad2尝试访问已卸载的 AppDomain。
在上面的程序中,我们实现了在一个AppDomain中读取另一个AppDomain中的对象,该对象继承自MarshalByRefObject。
观察上面的运行结果,可以看出有关MarshalByRefType的操作都是在创建它的AppDomain中完成的,并且在当前的AppDomain中得到的MarshalByRefType是一个代理对象,而非真实的对象。
2.2 按值封送(Marshal-by-Value)
我们已经知道了AppDomain之间如何按引用封送对象,按值封送和按引用封送的调用代码都是相同的,它们的区别是由被封送的类型决定的。
按引用封送:被封送的类型派生自System::MarshalByRefObject。
按值封送:被封送的类型不派生自System::MarshalByRefObject,并且该类和该类的所有字段都必须是可序列化的。
下来继续介绍按值封送。
using namespace System; using namespace System::Threading; using namespace System::Runtime::Remoting;//声明为序列化,否则不能传递 [Serializable] public ref class MarshalByRefValue : Object{ public:DateTime^ m_createTime;MarshalByRefValue(){m_createTime=DateTime::Now;Console::WriteLine("{0} ctor running in {1},Created on {2:D}",GetType()->ToString(),Thread::GetDomain()->FriendlyName,m_createTime);}virtual String^ ToString() override{Console::WriteLine("running in "+AppDomain::CurrentDomain->FriendlyName);return m_createTime->ToLongDateString();} };int main(array<System::String ^> ^args) {//使用Marshel-by-value进行通信//得到一个AppDomainAppDomain^ ad2=AppDomain::CreateDomain("ad2");//按值传递MarshalByRefValue^ mbrv=(MarshalByRefValue^) ad2->CreateInstanceAndUnwrap(MarshalByRefValue::typeid->Assembly->FullName,"MarshalByRefValue");Console::WriteLine("Is Proxy={0}",RemotingServices::IsTransparentProxy(mbrv));//false,说明得到的是实际引用,而非代理引用//调用方法Console::WriteLine(mbrv->ToString());AppDomain::Unload(ad2);try{Console::WriteLine(mbrv->ToString());Console::WriteLine("execute success");}catch(AppDomainUnloadedException^ e){Console::WriteLine(e->Message);}Console::ReadLine();return 0; }
运行结果:
MarshalByRefValue ctor running in ad2,Created on 2018年7月6日 Is Proxy=False running in ConsoleApplication14.exe 2018年7月6日 running in ConsoleApplication14.exe 2018年7月6日 execute success
通过上面的程序,我们不难观察出,按值传递的类型必须要序列化,否则不能传递(抛出异常)。我能定义了MarshalByRefValue类,该类和其字段(DateTime^ m_createTime)都是可序列化的。
在一个AppDomain中,把需要传递的类型和字段进行序列化,传到目前的AppDomain中,然后再进行反序列化,重新构建对象。
观察上面的程序的结果,可以看出MarshalByRefValue的所有操作都是在main方法的AppDomain中完成的,所以按值传送是把原来的AppDomain的对象序列化到目前的调用的AppDomain中。
3.卸载AppDomain
在上面的代码中,已经尝尝试过进行AppDomain的卸载操作。并不是所有的AppDomain都由程序员进行卸载(CLR COM服务器实例启动时默认创建的AppDomain直到应用程序结束时才由系统自动卸载)。卸载AppDomain会导致CLR卸载AppDomain中的所有的程序集,还会释放AppDomain中的loader堆。
卸载AppDomain的方法非常简单,可以直接调用AppDomain中的Unload方法。
4.监视AppDomain
宿主程序可监视AppDomain消耗的资源。有的宿主可根据这种信息判断一个AppDomain的内存或CPU消耗是否超过了应有的水准,并强制卸载它。
开始监视AppDomain的时候,需要将AppDomain的静态MonitorEnabled属性设置为true,从而显示打开监视。监视一旦打开就不能关闭,将MonitorEnabled设为false将会抛出ArgumentException异常。
AppDomain提供了以下四个只读属性来监视:
MonitoringSurvivedProcessMemorySize,这个Int64的静态属性返回当前CLR实例控制的所有AppDomain使用的字节数。这个数字只保证在上次垃圾回收时是准确的。
MonitoringTotalAllocatedMemorySize,这个Int64的实例属性返回特定AppDomain已分配的字节数。这个数字只保证在上一次回收时是准确的。
MonitoringSurvivedMemorySize,这个Int64实例属性返回特定AppDomain当前正在使用的字节数。这个数字只保证在上一次回收时是准确的。
MonitoringTotalProcessorTime,这个TimeSpan实例属性返回特定AppDomain的CPU占有率。
下面这个类检查两个时间点之间一个AppDomain发生的变化:
ref class AppDomainMonitorDelta{private:AppDomain^ m_appDomain;TimeSpan m_thisADCpu;Int64 m_thisADMemoryInUse;Int64 m_thisADMemoryAllocated;public:static AppDomainMonitorDelta(){AppDomain::MonitoringIsEnabled=true;}AppDomainMonitorDelta(AppDomain^ appDomain){this->m_appDomain=appDomain;this->m_thisADCpu=m_appDomain->MonitoringTotalProcessorTime;this->m_thisADMemoryInUse=m_appDomain->MonitoringSurvivedMemorySize;this->m_thisADMemoryAllocated=m_appDomain->MonitoringTotalAllocatedMemorySize;}~AppDomainMonitorDelta(){//定义析构函数 GC::Collect();Console::WriteLine("FriendlyName={0},CPU={1}ms",m_appDomain->FriendlyName,m_appDomain->MonitoringTotalProcessorTime.Subtract(m_thisADCpu));Console::WriteLine("Allocated {0:N0} bytes of which {1:N0} survived GCs",(m_appDomain->MonitoringTotalAllocatedMemorySize-m_thisADMemoryAllocated),(m_appDomain->MonitoringSurvivedMemorySize-m_thisADMemoryInUse));} };
然后就可以按照如下的姿势来调用了:
int main(array<System::String ^> ^args) {AppDomainMonitorDelta^ appDomainMonitor=gcnew AppDomainMonitorDelta(AppDomain::CurrentDomain);//监控当前AppDomain List<Object^>^ list=gcnew List<Object^>();for(Int32 x=0;x<10000;x++){list->Add(gcnew Object());}Int64 stop=Environment::TickCount+5000;//Environment::TickCount 获取系统启动后经过的毫秒数while(Environment::TickCount<stop);//持续工作5秒钟delete appDomainMonitor;//使用delete释放指针所指向的内存空间 Console::ReadLine();return 0; }
5.AppDomainFirstChance异常通知
每一个AppDomain都可以关联一组回调方法;CCLR开始查找AppDomain中的catch块时,这些回调方法得以调用。可用这些方法执行日志记录。另外,宿主可用这个机制监视AppDomain中抛出的异常。回调方法不能处理异常,也不能以任何形式吞噬异常;他们只是接受关于异常发生的通知。登记回调方法,只需要为AppDomain的实例事件FirstChanceException添加一个委托就可以了。
异常首次抛出时,CLR调用向抛出异常的AppDomain登记所有FirstChanceException回调方法。然后,CLR查找栈上在同一个AppDomain中的任何catch块。有一个catch块能处理异常,则异常处理完成,将继续正常执行,如果AppDomain中没有一个catch块能处理异常,则CLR沿着栈向上来调用AppDomain,再次抛出同一个异常对象(序列化和反序列化后)。这时感觉像抛出了一个全新的异常,CLR调用向当前AppDomain登记的所有FirstChanceException回调方法。这个过程会一直持续,直到抵达线程栈顶部。如果异常还未被处理,CLR只好终止整个进程。
using namespace System; using namespace System::Reflection; using namespace System::Runtime::Remoting; using namespace System::Threading; using namespace System::Runtime::ExceptionServices; using namespace std;[Serializable] public ref class TestClass :Object{ public:void someMethod(){try{int i=5;int j=0;//int k=i/j;throw "abc";}catch(InvalidCastException^ e){Console::WriteLine("{0} in remote AppDomain:{1}",AppDomain::CurrentDomain->FriendlyName,e->Message);}} }; static void Test(Object^ sender,FirstChanceExceptionEventArgs^ args){Console::WriteLine("{0} in FirstChanceException",AppDomain::CurrentDomain->FriendlyName); }; int main(array<System::String ^> ^args) {//绑定一个FirstChanceException事件AppDomain::CurrentDomain->FirstChanceException += gcnew EventHandler<FirstChanceExceptionEventArgs^>(Test);//创建一个AppDomain,保留和当前AppDomain一样的配置AppDomain^ ad2=AppDomain::CreateDomain("ad2");//得到实例TestClass^ mbro=(TestClass^)ad2->CreateInstanceAndUnwrap(TestClass::typeid->Assembly->FullName,TestClass::typeid->FullName);Console::WriteLine("Is Proxy={0}",RemotingServices::IsTransparentProxy(mbro));//falsetry{//调用方法mbro->someMethod();}catch(Exception^ e){Console::WriteLine("{0} in main:{1}",AppDomain::CurrentDomain->FriendlyName,e->Message);}Console::ReadLine();return 0; }
输出结果为:
Is Proxy=False ConsoleApplication15.exe in FirstChanceException ConsoleApplication15.exe in main:外部组件发生异常。
可以看出,FirstChanceException上登记的委托方法先执行,然后再执行catch块中的代码。
【CLR】解析AppDomain相关推荐
- 从CLR GC到CoreCLR GC看.NET Core为云而生
内存分配概要 前段时间在园子里看到有人提到了GC学习的重要性,很赞同他的观点.充分了解GC可以帮助我们更好的认识.NET的设计以及为何在云原生开发中.NET Core会占有更大的优势,这也是一个程序员 ...
- 从CLR GC到CoreCLR GC看.NET Core对云原生的支持
内存分配概要 前段时间在园子里看到有人提到了GC学习的重要性,很赞同他的观点.充分了解GC可以帮助我们更好的认识.NET的设计以及为何在云原生开发中.NET Core会占有更大的优势,这也是一个程序员 ...
- 【转】C#开发奇技淫巧三:把dll放在不同的目录让你的程序更整洁
转自:https://www.cnblogs.com/marvin/p/PutDllToSpecificFolder.html?utm_source=tuicool&utm_medium=re ...
- IIS Web 服务器/ASP.NET 运行原理基本知识概念整理
前言: 记录 IIS 相关的笔记还是从公司笔试考核题开始的,问 Application Pool 与 AppDomain 的区别? 促使我对进程池进了知识的学习,所以记录一下学习的笔记. 我们知道现在 ...
- 一本你必须知道的.net
<你必须知道的.NET> 作者简介:王涛 微软C# MVP,高级软件工程师,机械工程硕士,主要研究方向为.NET底层架构和企业级系统应 用.现就职于某软件公司负责架构设计.软件开发和项目管 ...
- 通俗易懂,什么是.NET?什么是.NET Framework?什么是.NET Core? .Net Web开发技术栈...
通俗易懂,什么是.NET?什么是.NET Framework?什么是.NET Core? 什么是.NET?什么是.NET Framework?本文将从上往下,循序渐进的介绍一系列相关.NET的概念,先 ...
- [AX]AX2012 使用.NET程序集部署
在.NET Interop from X++一文中有提到X++可以引用的.NET程序集,这里就这个问题更加深入的探讨. 前文中说到X++所引用的.NET程序集需要手工拷贝到Client\bin目录下, ...
- (17万浏览量) .NET Core的介绍
.NET Core基础理论 1..NET Core基础理论 1.1.重要工具 .NET FlatForm下的项目:https://github.com/dotnet/ 查看.NET Core源码:ht ...
- VS2010自定义新建文件模版
不知不觉VS2010已经成为.NET开发人员的必备工具,相比经典版VS2005,到过渡版VS2008,2010在性能稳定性和易用性上都得到很大的提高. 结合VS工具,其下的插件也层出不穷.今天重点给大 ...
最新文章
- 帧中继中配EIGRP(hub-spoke)
- [bzoj3625][Codeforces Round #250]小朋友和二叉树 (生成函数)
- mybatis自己学习的一些总结
- 灯泡亮度控制单片机_海淀区投影机灯泡
- ServiceComb java-chassis和CSE java-chassis的区别
- (转)Spring的概述
- jdbc驱动类加载直接指定线程上下文加载器加载
- 关于计算机实验的英语作文,关于实验的英语作文
- 操作系统随机密码,定时改密码
- linux服务器用的多的命令,linux服务器常用命令
- 黑客帝国代码雨的实现
- 华为主题锁屏壁纸换不掉_华为手机的不明照片是哪来的?这几个设置得关闭,否则128G也不够...
- 利用threading多线程爬取王者荣耀的高清壁纸
- python信用卡客户_银行信用卡客户价值分析(Python数据分析)
- 第九周项目三:星星图案(一)
- 微信小程序直播 OBS Studio 26.0.2 官方版推流软件
- android相对控件居中对齐,相对布局(RelativeLayout)常用属性
- ntoskrnl.exe损坏或丢失的解决方案
- 红米5双清_红米手机双清方法
- linux kfifo的使用
热门文章
- Proxmark3 Easy Gui 4.0 5.0 5.1全卡克隆已解密的IC卡
- NB-IOT技术以及物联网安全问题简述
- VB 获取光标在TextBox、RichTextBox中所在的位置
- CNCC2018:[早鸟票]倒计时两周,7000人盛会日程抢先看!
- 017-Centos7.6+CDH 6.2 安装和使用
- IP基础及ping命令
- FIFO、UART、ALE解释
- android添加购物车动画、天气应用、渐变状态栏、文件选择器等源码
- apache配置多https域名对应单个证书和多个不同的https域名对应多个不同的证书
- 基于HFC实现10Gbps对称数据传输