与前面所述的宿主接口不同,CLR提供一组非托管COM接口,允许在CLR执行引擎中加入工具、检查,甚至干涉其执行。这些接口分成两套:一套被裁剪为调试器(debugger),另一套则裁剪为探测器(profiler)。然而在这两者之间,你能够向在CLR外部运行的非托管代码公开CLR的所有方面。
图10.10展示了CLR调试器的对象模型。你可以通过在CLSID_CorDebug上调用CoCreetelnstance方法获取ICorDebug接口,这个COM类被MSCORDBI.DLL公开,并且与运行在任何进程中的CLR发生联系。调试器在被CLR实例附加之后,可以注册最多两个回调接口,在运行程序中(例如,加载器活动、AppDomain的创建与卸载)出现了较为粗糙(coarse-grained)的事件,以及诸如遭遇断点等调试器相关事件时,CLR使用其中的一个接口ICorDebugManagedCallback通知与之有关的调试器。CLR使用第二种事件接口ICorDebugUnManagedCallback,表明Win32调试器事件。CLR只有在CLR调试器还附加为本机Win32调试器时,才使用这种接口中。
在你的程序附加到CLR,作为其调试器之后,运行程序的整个状态都是有效的。图10.11展示CLR以调试接口为视角的对象模型。这个对象模型对于CLR的概念模型仍是正确的,只是它允许对运行程序的执行状态更细致地访问,下至注册级别。
CLR调试基础架构(debugging infrastructure)激活的事件略微有些粗糙。需要更好粒度(granularity)的程序则要使用探测器接口套件。在初始化之上,CLR在注册表或者进程环境变量中查找两个配置属性。其中一个属性Cor_Enable_Profiling,控制CLR是否加载探测器DLL,用于指示CLR。第二个属性Cor.Profilier标明被加载的探测器DLL的COM CLSID。如图10.12所示,DLL必须实现ICorProfilerCallback接口。在初始化之上,探测器DLL必须提供一个位掩码以标明所期望接收的是哪一个事件通知。
在这个位掩码中,每一个可能的事件类型都有一个独特的标记,允许探测DLL控制如何干预程序。表10.4展示了一组探测器事件通知。注意,较为细致的通知是MethodEnter和IMethodLeave。它们允许探测DLL截取在CLR中出现的每一个方法调用的信息。
在我们谈到探测基础架构(profiling infrastructure)时,很难不讨论方法内联(method inlining)。CLR模块带有如何生成代码的元数据特性。这个元数据特性就是System.DiagnosticsDebuggableAttrlbute,通过编译器的命令行开关(/debug和/optimaize)进行控制。这个特性具有两个属性:IsJITTrackingEnabled和IsJITOptimizeDisabied。
IsJITTrackjngEnabled通知JIT编译器发射每指令表(per-instruction table),用于调试基础架构。这使得调试器能够较好地步进源代码,不过它增加了程序在内存中所占的空间。可以通过设置/debug或者/debug+编译开关将这个属性设为true。这个属性默认为taise,不过,你也可以通过/debug或者/debug:pdbonly开关将其显式地设为false。这两个选项的后者生成符号化的调试信息,而不管JIT编译器会由此发射出不很精确的调试信息。
IsJITOptimizerDiabled通知JIT编译器消除方法体的内联扩展。这样.探测器就能够获得程序中哪些方法体是实际的热点(hotspot)的精确信息。然而,使内联无效将增加方法调用的开销,对一些小的方法体而言,这可能是方法的主要开销。IsJITOptimizerDiabled默认情况下为true,但可以通过/optimaize或者/optimaize+编译器开关将其设置为false。
最后,前面描述的母模块设置可以用每应用积序(per-Application)配置文件覆盖。这个文件采用典型的windows Int语法,如下所示:
[.NET Framework Debugging Control]
AllowOptimize=1
GenerateTrackingInfo=1
如果目标执行体被Application.exe调用,那么该文件的名字必须是Application.INI,并且与exe文件存放在同一目录下。
我们走到哪儿了CLR最终是一组Win32/COM DLL,可以被加载到任何Win32进程中,CLR主要的正面外观(facade)是MSCOREE.DLL,它在实际的运行时之前,充当轻量级垫片(shim),而实际的运行时则是由MSCORWKS.DLL和MSCORSVR.DLL(主要)实现。在CLR加载你的程序后,最好是采用托管的执行模式,当然也可以随时调用非托管的方法。