【CLR】解析AppDomain
目录结构:
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 引用一个有效的代理对象;代理对象引用一个无效的AppDomain
try{
mbrt->SomeMethod();//在代理对象上调用这个方法。AppDomain无效,造成抛出异常。
}catch(AppDomainUnloadedException^ e){
Console::WriteLine(e->Message);
}
Console::ReadLine();
return ;
}
运行结果:
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进行通信 //得到一个AppDomain
AppDomain^ 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 ;
}
运行结果:
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=;x<;x++){
list->Add(gcnew Object());
} Int64 stop=Environment::TickCount+;//Environment::TickCount 获取系统启动后经过的毫秒数
while(Environment::TickCount<stop);//持续工作5秒钟 delete appDomainMonitor;//使用delete释放指针所指向的内存空间
Console::ReadLine();
return ;
}
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=;
int j=;
//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));//false try{
//调用方法
mbro->someMethod();
}catch(Exception^ e){
Console::WriteLine("{0} in main:{1}",AppDomain::CurrentDomain->FriendlyName,e->Message);
} Console::ReadLine();
return ;
}
输出结果为:
Is Proxy=False
ConsoleApplication15.exe in FirstChanceException
ConsoleApplication15.exe in main:外部组件发生异常。
可以看出,FirstChanceException上登记的委托方法先执行,然后再执行catch块中的代码。
【CLR】解析AppDomain的更多相关文章
- 【C#进阶系列】22 CLR寄宿和AppDomain
		
关于寄宿和AppDomain 微软开发CLR时,将它实现成包含在一个DLL中的COM服务器. 任何Windows应用程序都能寄宿(容纳)CLR.(简单来讲,就是CLR在一个DLL中,通过引用这个DLL ...
 - CLR寄宿和AppDomain
		
一.CLR寄宿 .net framework在windows平台的顶部允许.者意味着.net framework必须用windows能理解的技术来构建.所有托管模块和程序集文件必须使用windows ...
 - 第22章 CLR寄宿和AppDomain
		
22.1 CLR寄宿 CLR Hosting(CLR 宿主)的概念:初始启动.Net Application时,Windows进程的执行和初始化跟传统的Win32程序是一样的,执行的还是非托管代码,只 ...
 - 重温CLR(十六) CLR寄宿和AppDomain
		
寄宿(hosting)使任何应用程序都能利用clr的功能.特别要指出的是,它使现有应用程序至少能部分使用托管代码编写.另外,寄宿还为应用程序提供了通过编程来进行自定义和扩展的能力. 允许可扩展性意味着 ...
 - C#知识点总结系列:5、CLR的组成和运转
		
clr基本 CLR(Common Language Runtime)是一个可由多种编程语言使用的“运行时”.(例如:c#,c++/cli,vb,f#,ironpython,ironruby,il... ...
 - C#学习笔记----AppDomain应用程序域
		
使用.Net建立的可执行程序*.exe,并没有直接承载到进程当中,而是承载到应用程序域(AppDomain)当中.应用程序域是.Net引入的一个新概念,它比进程所占用的资源要少,可以被看做是一个轻量级 ...
 - 第二节:AppDomain
		
CLR COM服务器初始化时,会创建一个AppDomain.AppDomain是一组程序集的逻辑容器.CLR初始化时创建的第一个AppDomain称为默认的AppDomain,这个默认的AppDoma ...
 - 【CLR VIA C#】读书笔记
		
工作几年了才看,记录下笔记备忘. 章节 笔记 1.CLR的执行模型 公共语言运行时(Common Language Runtime,CLR) 源代码-->编译器检查语法和分析源代码-->托 ...
 - C#的CLR组成和运转介绍
		
原文 clr基本 CLR(Common Language Runtime)是一个可由多种编程语言使用的“运行时”.(例如:c#,c++/cli,vb,f#,ironpython,ironruby,il ...
 
随机推荐
- 表达式树ExpressionTrees
			
简介 表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和 x < y 这样的二元运算等.你可以对表达式树中的代码进行编辑和运算.这样能够动态修改可执行代码.在不同数据库 ...
 - POJ 1017 Packets【贪心】
			
POJ 1017 题意: 一个工厂制造的产品形状都是长方体,它们的高度都是h,长和宽都相等,一共有六个型号,他们的长宽分别为 1*1, 2*2, 3*3, 4*4, 5*5, 6*6. 这些产品通常 ...
 - #2 codeforces 480 Parcels
			
题意: 就是有一个用来堆放货物的板,承重力为S.现在有N件货物,每件货物有到达的时间,运走的时间,以及重量,承重,存放盈利.如果这件货物能再运达时间存放,并在指定时间取走的话,就能获得相应的盈利值.货 ...
 - A. 【UR #17】滑稽树上滑稽果
			
题解: 首先很显然的是这是一条链(特殊数据说是链是故意让人迷茫的??) 然后 自己就开始yy 觉得每一次是加入一个使得当前值最小的数 然而这tm又是特殊数据?? 那就写一波发现是错的 考虑一下特殊数据 ...
 - Django2.0 path与Django1.x版本url正则匹配问题
			
2.0内的path匹配正则时候无效, 导入re_path即可匹配正则
 - zprofiler三板斧解决cpu占用率过高问题
			
zprofiler三板斧解决cpu占用率过高问题 九居 浏览 171 2015-04-08 14:11:58 发表于:JVM性能与调试平台 zprofiler 上周五碰到了一个线上机器cpu ...
 - 【Java】 剑指offer(48) 最长不含重复字符的子字符串
			
本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集 题目 请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字 ...
 - IPV4闪退
			
如果出现这种状况,在安全模式下重注册dll 运行->输入cmd->输入 for %1 in (%windir%\system32\*.dll) do regsvr32.exe /s %1 ...
 - vue 开发环境搭建,超级简单仅需3步。
			
1,打开 http://nodejs.cn/download/ 下载 nodejs,并安装. 2,成功以后,启动cmd命令行,输入npm install -g cnpm --registry=htt ...
 - BZOJ.4894.天赋(Matrix Tree定理 辗转相除)
			
题目链接 有向图生成树个数.矩阵树定理,复习下. 和无向图不同的是,度数矩阵改为入度矩阵/出度矩阵,分别对应外向树/内向树. 删掉第i行第i列表示以i为根节点的生成树个数,所以必须删掉第1行第1列. ...