CLR via C#读书笔记 CLR寄宿和AppDomain

时间:2022-12-28 17:25:53

寄宿

寄宿是指让其他应用程序(非托管代码)使用CLR的能力,比如自己用C++开发的窗体能创建CLR实例。

托管代码也能调用非托管代码


   
   
  1. [DllImport("kernel32.dll")]
  2. public static extern int WinExec(string exeName, int operType);

通常会调用win32 api,但是要查文档才知道怎么定义extern方法

CLR实际上被实现为COM服务器,可以通过CoCreateInstanceCLRCreateInstance(推荐)创建CLR COM服务器实例,而宿主就能使用CLR的东西。

CLRCreateInstanceMSCorEE.dll里面(在安装.Net Framework时被到系统目录中),他的工作决定创建哪个版本的CLR(这个dll与.Net一起安装,但却是唯一的,安装了多个版本的.Net电脑中这个dll总是最新版本)

无论是托管程序还是非托管程序,他们都会编译成PE文件(保存程序的自描述信息,比如入库函数),而托管程序开始执行时会有一条JMP指令跳转到MSCorEE.dll里,通过MSCorEE.dll的PE文件信息找到这个_CorExeMain函数的入口地址,然后修改刚才的JMP指令要跳转的地址,从而将控制跳转到了_CorExeMain这个函数里面去。随后CLR被启动,接着根据托管程序的CLR表头找到入口地址,再跳转进去,程序开始运行

AppDomain

程序集逻辑容器
有点像进程,提供的数据和代码隔离,但创建的代价比真正创建进程小。

  • 不能访问其他AppDomain创建的对象
  • 可卸载
  • 可单独配置
    • 这些配置涉及搜索路径,版本绑定重定向,加载程序集的方式
  • 可单独保护
    • 有单独的权限集

CLR via C#读书笔记 CLR寄宿和AppDomain

  • CLR启动时会创建一个默认的AppDomain
  • AppDomain中有个Loader堆,记录该AppDomain创建以来访问过了哪些类型(类型对象)
  • 频繁被使用的类比如Object,它的类型对象会保存在特殊的AppDomain中被其他AppDomain共享,代价是这个AppDomain永远不会被卸载
  • 注意多个AppDomain是用同一个托管堆

跨程序域通信

AppDomain的核心是隔离但是它还是提供了跨AppDomain访问对象的方法

假设一个情景,在AppDomain A中去访问AppDomain B的对象


  
  
  1. AppDomain appDomainB = AppDomain.CreateDomain("NewDomain");
  2. DemoClass obj;
  3. // 在新的应用程序域中创建对象
  4. obj = (DemoClass)appDomainB.CreateInstanceAndUnwrap("ClassLib", "ClassLib.DemoClass");

这里会抛出异常:"ClassLib.DemoClass"未标记为可序列化

其实这个过程叫做按值封送,这个过程中会创建两个对象,在AppDomain B中创建一个,然后序列化成字节数组,传给AppDomain A,再反序列化成对象,这种做法要求DemoClass必须被打上可序列化特性。

还有一种封送方式叫按引用封送,这种方式要求DemoClass派生自MarshalByRefObject
还是用上面一样的代码,这个过程本质是创建了两个对象,在AppDomain A中创建了个代理对象obj,AppDomainB也创建个对象(这是真正的对象),在B向A发送对象引用前会做以下操作:

  • 在AppDomain A的Loader堆中定义一个代理对象的类型对象(有与AppDomain B的ClassLib.DemoClass类的类型对象完全一样的实例成员:方法,属性,事件,但不包括实例字段)
    • 实际上内部会通过代理类找到真正的对象,再用反射获得字段值
  • 定义代理类对象自己的实例字段(保存了那个AppDomain拥有真实的对象,以及如何找到他)

访问对象时(这是个同步的过程),利用代理类的信息,切换线程到B,获得真正的对象,调用真正的方法

卸载AppDomain

通过AppDomain.Unload方法

过程:

  • 挂起执行托管代码的所有线程
  • 检查与被卸载AppDomain有关系的线程,强迫它抛出异常ThreadAbortException
    • 如果抛出的异常没被处理,异常会被CLR”吞噬”,线程终止,进程继续执行
  • 找到所有创建的代理对象,给它们设置个flag,之后所有尝试调用代理对象方法的操作都会抛异常
  • 强制启动垃圾回收
  • 恢复剩余线程

监视AppDomain

通过修改AppDomain的静态属性MonitoringEnabled为true来启动监视,一旦启动就不能关闭.
启动后可以读取下面4个属性

  • MonitoringSurivedProcessMemorySize 当前CLR实例控制的所有AppDomain使用的字节数
  • MonitoringTotalAllocatedMemorySize 已分配的字节数
  • MonitoringSurvivedMemorySize 正则使用的字节数
  • MonitoringTotalProcessorTime CPU占用率

异常通知

通过给AppDomain.FirstChanceException事件注册方法能在AppDomain抛异常时接到通知
这个时机是:在异常抛出后查找catch块前,FirstChanceException不能处理异常,AppDomain抛异常后会寻找catch,如果找不到则将异常抛给调用该AppDomain的AppDomain(中间有跨AppDomain传递),如果一直到线程栈顶都找不到catch块,CLR将被终止

宿主如何使用AppDomain

  • winform,wpf,控制台都是自寄宿(self-host)的,在本文开头说过,启动exe时会加载MSCorEE.dll紧接着启动CLR,初始化后再运行Main方法
  • Asp.Net中当用户请求aspx时会被iis转交给aspnet_isapi.dll(非托管代码),它负责启动CLR,之后Asp.Net判断该Web应用是否是第一次被请求,如果是则创建AppDomain,并以虚拟根目录来标识。所以默认情况下,一个进程是可以跑多个网站(AppDomain)。
    • Asp.Net的一个亮点是允许不关闭服务器的前提下动态更改网站代码,当Asp.Net检测出文件被修改后则会卸载就的AppDomain并创建新的AppDomain(加载新的文件),为了确保这一过程顺利执行,Asp.Net使用了一个名为shadow copying1的功能

资料

TODO

  • .net Romoting(不同计算机,不同进程,跨AppDomain访问对象技术)
  • SQLServer可以用托管代码写存储过程?

  1. 在加载程序集时,先把程序集复制到Cache目录下,再加载,这样原程序集不会被锁定。