前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《CLR via C#》笔记:第4部分 核心机制(3)

《CLR via C#》笔记:第4部分 核心机制(3)

作者头像
[Sugar]
发布2022-09-21 14:41:10
8000
发布2022-09-21 14:41:10
举报
文章被收录于专栏:U3D技术分享
  • 本博客所总结书籍为《CLR via C#(第4版)》清华大学出版社,2021年11月第11次印刷(如果是旧版书籍或者pdf可能会出现书页对不上的情况)
  • 你可以理解为本博客为该书的精简子集,给正在学习中的人提供一个“glance”,以及对于部分专业术语或知识点给出解释/博客链接。
  • 【本博客有如下定义“Px x”,第一个代表书中的页数,第二个代表大致内容从本页第几段开始。(如果有last+x代表倒数第几段,last代表最后一段)】
  • 电子书可以在博客首页的文档-资源归档中找到,或者点击:传送门自行查找。如有能力请支持正版。(很推荐放在竖屏上阅读本电子书,这多是一件美事)
  • 欢迎加群学习交流:637959304 进群密码:(CSGO的拆包密码) 

目录

第二十二章 CLR寄宿和AppDomain

  • Framework的巨大价值。寄宿(hosting)使任何应用程序都能利用CLR 的功能。特别要指出的是,它使现有的应用程序至少能部分使用托管代码编写。另外,寄宿还为应用程序提供了通过编程来进行自定义和扩展的能力。
  • 允许可扩展性意味着第三方代码可在你的进程中运行。在 Windows中将第三方DLL加载到进程中意味着冒险。DLL中的代码很容易破坏应用程序的数据结构和代码。DLL还可能企图利用应用程序的安全上下文来访问它本来无权访问的资源。CLR的AppDomain功能解决了所有这些问题。AppDomain 允许第三方的、不受信任的代码在现有的进程中运行,而CLR保证数据结构、代码和安全上下文不被滥用或破坏。
  • 程序员经常将寄宿和AppDomain与程序集的加载和反射一起使用。

CLR寄宿

  • .NET Framework在 Windows平台的顶部运行。这意味着.NET Framework必须用Windows能理解的技术来构建。首先,所有托管模块和程序集文件都必须使用Windows PE 文件格式,而且要么是Windows EXE文件,要么是DLL文件。(P489 last2)
  • 任何Windows应用程序都能寄宿(容纳)CLR。但不要通过调用CoCreateInstance来创建CLR COM服务器的实例,相反,你的非托管宿主应该调用MetaHost.h文件中声明的CLRCreateInstance函数。CLRCreateInstance 函数在 MSCorEE.dll 文件中实现,该文件一般在C:\Windows\System32目录中。这个DLL被人们亲切地称为“垫片”(shim),它的工作是决定创建哪个版本的CLR;垫片DLL本身不包含CLR COM服务器。(P490 2)一台机器可以安装多个CLR,但只能有一个版本的垫片。

AppDomain

  • CLR COM服务器初始化时会创建一个AppDomain。AppDomain是一组程序集的逻辑容器。CLR初始化时创建的第一个AppDomain称为“默认AppDomain”,这个默认的AppDomain只有在Windows进程终止时才会被销毁。(P491 last2)
  • 除了默认 AppDomain,正在使用非托管COM接口方法或托管类型方法的宿主还可要求CLR创建额外的 AppDomain。AppDomain是为了提供隔离而设计的。下面总结了AppDomain的具体功能: 1、一个AppDomain的代码不能直接访问另一个AppDomain代码创建的对象。 2、AppDomain可以卸载。但指定卸载AppDomain中的程序集不可以。 3、AppDomain可以单独维护。 4、AppDomain可以单独配置。
  • 寄宿了CLR两个AppDomain的一个Windows进程示意图: 两个AppDomain都加载了System.dll程序集。如果这两个AppDomain都使用了来自System.dll 的一个类型,那么两个AppDomain的 Loader堆会为相同的类型分别分配一个类型对象;类型对象的内存不会由两个AppDomain共享。(P493 2) 针对以“AppDomain中立”的方式加载的程序集,CLR 会为它们维护一个特殊的 Loader堆。该Loader 堆中的所有类型对象,以及为这些类型定义的方法JIT编译生成的所有本机代码,都会由进程中的所有AppDomain共享。但因为是AppDomain中立程序集内容会共享给所有AppDomain,因此里面的程序集不能卸载,只能等待进程结束之后让Windows回收。(P493 last)
image 75 - 《CLR via C#》笔记:第4部分 核心机制(3)
image 75 - 《CLR via C#》笔记:第4部分 核心机制(3)
  • 跨越AppDomain边界访问对象-通过良好定义的机制进行。代码示例(按引用封送,按值封送,完全不能封送)(P494 – P504)

卸载AppDomain

  • AppDomain很强大的一个地方就是可以卸载它。卸载AppDomain会导致CLR卸载AppDomain中的所有程序集,还会释放AppDomain的 Loader堆。卸载AppDomain的办法是调用AppDomain的静态Unload方法。这导致CLR执行一系列操作来得体地卸载指定的AppDomain。具体操作:(P504 2) 1、CLR挂起进程中执行过托管代码的所有线程。 2、CLR检查所有线程栈,查看那些线程正在执行要卸载的AppDomain中的代码,或者哪些线程会在某个时候返回至要卸载的AppDomain。 3、当2中发现的所有线程都离开AppDomain后,CLR遍历堆,为引用了“由已卸载的AppDomain创建的对象”的每个代理对象都设置一个标志(flag)。这些代理对象现在知道它们引用的真实对象已经不在了。现在,任何代码在无效的代理对象上调用方法都会抛出一个AppDomainUnloadedException异常。 4、CLR强制垃圾回收,回收由已卸载的AppDomain创建的任何对象的内存。这些对象的Finalize方法被调用,使对象有机会正确清理它们占用的资源。 5、CLR恢复剩余所有线程的执行。调用AppDomain.Unload方法的线程将继续运行;对AppDomain.Unload的调用是同步进行的。

监视AppDomain

  • 宿主应用程序可监视AppDomain消耗的资源。由于AppDomain监视本身也会产生开销,所以宿主必须将AppDomain 的静态 MonitoringEnabled属性设为true,从而显式地打开监视。监视一旦打开便不能关闭;将MonitoringEnabled属性设为false 会抛出一个ArgumentException异常。(P505 last2)

AppDomain FirstChance异常通知

  • 每个AppDomain都可关联一组回调方法;CLR开始查找AppDomain中的catch 块时,这些回调方法将得以调用。可用这些方法执行日志记录操作。另外,宿主可利用这个机制监视AppDomain中抛出的异常。回调方法不能处理异常,也不能以任何方式“吞噬”异常(装作异常没有发生);它们只是接收关于异常发生的通知。要登记回调方法,为AppDomain的实例事件FirstChanceException添加一个委托就可以了。(P507 1)
  • 下面描述了CLR如何处理异常:异常首次抛出时,CLR 调用向抛出异常的AppDomain 登记的所有FirstChanceException回调方法。然后,CLR查找栈上在同一个AppDomain中的任何 catch 块。有一个catch 块能处理异常,则异常处理完成,将继续正常执行。如果AppDomain中没有一个catch 块能处理异常,则CLR沿着栈向上来到调用AppDomain,再次抛出同一个异常对象(序列化和反序列化之后)。这时感觉就像是抛出了一个全新的异常,CLR 调用向当前AppDomain登记的所有FirstChanceException回调方法。这个过程会一直持续,直到抵达线程栈顶部。届时如果异常还未被任何代码处理,CLR只好终止整个进程。(P507 2)

宿主如何使用AppDimain

  • 可执行应用程序,Microsoft Silverlight富 Internet应用程序,Microsoft ASP.NET和XML Web服务应用程序,Microsoft SQL Server。(P597-P509)

高级宿主控制

  • 使用托管代码管理CLR(P509 last)
  • 写健壮的宿主应用程序(P510 last2)
  • 宿主如何拿回它的线程:宿主应用程序一般都想保持对自己的线程的控制。以一个数据库服务器为例。当一个请求抵达数据库服务器时,线程A获得请求,并将该请求派发(dispatch)给线程B以执行实际工作。线程B可能要执行并不是由数据库服务器的开发团队创建和测试的代码。例如,假定一个请求到达数据库服务器,要执行由运行服务器的公司用托管代码写的存储过程。数据库服务器要求存储过程在自己的AppDomain中运行,这个设计自然是极好的,因为能保障安全,防止存储过程访问其AppDomain外部的对象,还能防止代码访问不允许访问的资源(比如磁盘文件或剪贴板)。(P511 last) 但是,如果存储过程的代码进入死循环怎么办?在这种情况下,数据库服务器把它的一个线程派发给存储过程代码,但这个线程一去不复返。这便将数据库服务器置于一个危险的情况。 为了解决这些问题,宿主可利用线程终止功能。
image 76 - 《CLR via C#》笔记:第4部分 核心机制(3)
image 76 - 《CLR via C#》笔记:第4部分 核心机制(3)

宿主如何拿回线程

  • 宿主拿回线程: 1、客户端向服务器发送请求 2、服务器线程获得请求,把它派发给一个线程池线程来执行实际工作 3、线程池线程获得客户端的请求,执行由构建并测试宿主应用程序的那个公司写的可信代码(trusted code) 4、可信代码进入try块。 5、宿主会记录接收到客户端请求的时间。不可信代码在管理员设定的时间内没有对客户端做出响应,宿主就会调用Thread 的Abort方法要求CLR中止线程池线程,强制它抛出一个 ThreadAbortException 异常。 6、线程池线程开始展开(unwind),调用finally 块,使清理代码得以执行。 7、为了响应捕捉到的ThreadAbortException异常,宿主调用Thread 的 ResetAbort方法。 8、宿主的代码已捕捉到ThreadAbortException异常。因此,宿主可向客户端返回某种形式的错误,允许线程池线程返回线程池,供未来的客户端请求使用。

本章节主要了解反射的概念,其余内容一概不属于Unity范畴内的常用内容,故阅读一遍即可,本文只记录大纲。

第二十三章 程序集加载和反射

  • 本章讨论了在编译时对一个类型一无所知的情况下,如何在运行时发现类型的信息、创建类型的实例以及访问类型的成员。可利用本章讲述的内容创建动态可扩展应用程序。在这种情况下,一般是由一家公司创建宿主应用程序,其他公司创建加载项(add-in)来扩展宿主应用程序。宿主不能基于一些具体的加载项来构建和测试,因为加载项由不同公司创建,而且极有可能是在宿主应用程序发布之后才创建的。这是宿主为什么要在运行时发现加载项的原因。(P515 1)

程序集加载

  • JIT编译器将方法的代码编译成本机代码时,会查看L代码中引用了哪些类型。在运行时,JIT编译器利用程序集的TypeRef和AssemblyRef元数据表来确定哪一个程序集定义了所引用的类型。(P516 1)
  • 在内部,CLR使用System.Reflection.Assembly类的静态Load方法尝试加载这个程序集。(P516 2)
  • 调用Assembly 的LoadFrom方法加载指定了路径名的程序集。(P517 2)
  • ReflectionOnlyLoadFrom方法加载由路径指定的文件;文件的强名称标识不会获取,也不会在GAC和其他位置搜索文件。(P518 last2)

使用反射构建动态可扩展应用程序

  • 元数据是用一系列表来存储的。生成程序集或模块时,编译器会创建一个类型定义表、一个字段定义表、一个方法定义表以及其他表。利用System.Reflection命名空间中包含的类型,可以写代码来反射(或者说“解析”)这些元数据表。(P520 2)
  • 只有极少数应用程序才需要使用反射类型。

反射的性能

  • 反射是相当强大的机制,允许在运行时发现并使用编译时还不了解的类型及其成员。但是,它也有下面两个缺点:(P521 1) 1、反射造成编译时无法保证类型安全性。 2、反射速度慢。
  • 建议的动态法相和构造类型实例的技术方法:(P521 3) 1、让类型从编译时已知的基类型派生。 2、让类型实现编译时已知的接口。
  • 发现程序集中定义的类型:反射经常用用于判断程序集定义了哪些类型。(P522 1)
  • 类型对象的准确含义:(P522 last) 1、Object的GetType方法 2、System.Type类型提供了静态GetType方法的几个重载版本 3、System.Type类型提供了静态ReflectionOnlyGetType方法 4、System.TypeInfo类型提供了实例成员DeclaredNestedTypes和 GetDeclaredNestedType 5、System.Reflection.Assembly类型提供了实例成员GetType,DefinedTypes和ExportedTypes 。
  • 构建Exception派生类型的层次结构。(P524 last)
  • 构造类型的实例:获得对Type派生对象的引用之后,就可以构造该类型的实例了。FCL提供了以下几个机制。(P525 1) 1、System.Activator的 CreatelnstanceFrom方法 2、System.Activator的 Createlnstance方法 3、System.AppDomain的方法:AppDomain类型提供了4个用于构造类型实例的实例方法(每个都有几个重载版本),包括CreateInstance,CreateInstanceAndUnwrap,CreateInstanceFrom和CreateInstanceFromAndUnwrap。 4、System.Reflection.Constructorlnfo 的Invoke实例方法
  • 利用前面列出的机制,可为除数组(System.Array派生类型)和委托(System.MulticastDelegate派生类型)之外的所有类型创建对象。创建数组需要调用Array的静态 CreateInstance方法(有几个重载的版本)。所有版本的CreateInstance方法获取的第一个参数都是对数组元素Type的引用。CreateInstance的其他参数允许指定数组维数和上下限的各种组合。创建委托则要调用MethodInfo的静态CreateDelegate方法。所有版本的CreateDelegate方法获取的第一个参数都是对委托Type的引用。CreateDelegate方法的其他参数允许指定在调用实例方法时应将哪个对象作为this参数传递。(P526 last2)
  • 构造泛型类型的实例首先要获取对开放类型的引用,然后调用Type的 MakeGenericType方法并向其传递一个数组(其中包含要作为类型实参使用的类型)°。然后,获取返回的Type对象并把它传给上面列出的某个方法。(P526 last)

设计支持加载项的应用程序

  • 构建可扩展应用程序时,接口是中心。可用基类代替接口,但接口通常是首选的,因为它允许加载项开发人员选择他们自己的基类。(P257-259)

使用反射发现类型的成员

  • 字段、构造器、方法、属性、事件和嵌套类型都可以定义成类型的成员。FCL包含抽象基类System.Reflection.MemberInfo,封装了所有类型成员都通用的一组属性。MemberInfo有许多派生类,每个都封装了与特定类型成员相关的更多属性。下图是这些类型的层次结构。
image 77 - 《CLR via C#》笔记:第4部分 核心机制(3)
image 77 - 《CLR via C#》笔记:第4部分 核心机制(3)

封装了类型成员信息的反射类型层次结构

  • 查询类型的成员并显示成员的信息,代码示例(P530 last)
  • 调用类型的成员(P533-P537)
  • 使用绑定句柄减少进程的内存消耗:许多应用程序都绑定了一组类型(Type对象)或类型成员(MemberInfo派生对象),并将这些对象保存在某种形式的集合中。以后,应用程序搜索这个集合,查找特定对象,然后调用(invoke)这个对象。这个机制很好,只是有个小问题:Type和 MemberInfo派生对象需要大量内存。所以,如果应用程序容纳了太多这样的对象,但只是偶尔调用,应用程序消耗的内存就会急剧增加,对应用程序的性能产生负面影响。(P537 last2) CLR内部用更精简的方式表示这种信息。CLR之所以为应用程序创建这些对象,只是为了方便开发人员。CLR不需要这些大对象就能运行。如果需要保存/缓存大量 Type 和MemberInfo派生对象,开发人员可以使用运行时句柄(runtime handle)代替对象以减小工作集(占用的内存)。(P537 last)
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年7月19日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第二十二章 CLR寄宿和AppDomain
    • CLR寄宿
      • AppDomain
        • 卸载AppDomain
          • 监视AppDomain
            • AppDomain FirstChance异常通知
              • 宿主如何使用AppDimain
                • 高级宿主控制
                • 第二十三章 程序集加载和反射
                  • 程序集加载
                    • 使用反射构建动态可扩展应用程序
                      • 反射的性能
                        • 设计支持加载项的应用程序
                          • 使用反射发现类型的成员
                          相关产品与服务
                          数据库专家服务
                          数据库专家服务(Database Expert Service,DBexpert)为您提供专业化的数据库服务。仅需提交您的具体问题和需求,即可获得腾讯云数据库专家的专业支持,助您解决各类专业化问题。腾讯云数据库专家服务团队均有10年以上的 DBA 经验,拥有亿级用户产品的数据库管理经验,以及丰富的服务经验。
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档