CLR上的接口调用也是在运行时检查的

xiaoxiao2026-08-30  19

作者:RednaxelaFX 主页:[url]http://rednaxelafx.iteye.com[/url] 日期:2009-06-02 系列笔记: [url=http://rednaxelafx.iteye.com/blog/400362]JVM在校验阶段不检查接口的实现状况[/url] [url=http://rednaxelafx.iteye.com/blog/400597]为什么JVM与CLR都不对接口方法调用做静态校验?[/url] 刚才的一帖,[url=http://rednaxelafx.iteye.com/blog/400362]JVM在校验阶段不检查接口的实现状况[/url],我提到JVM在处理invokeinterface时,如果遇到被调用对象没有实现指定的接口时,在运行时抛出异常的行为。那么.NET的CLR在这方面的行为又如何呢? CLR对MSIL的校验是与JIT同时进行的。JIT编译器一边检查代码的正确性,一边生成native code;一个方法被调用时,如果还没有JIT过的话,要在JIT之后才进入运行状态。于是CLR执行托管方法可以分为一个[加载-链接-校验-JIT]的阶段与一个[执行]的阶段。与JVM一样,如果CLR遇到被调用对象没有实现指定的接口的状况,也是在运行时抛出异常的。 要观察这个行为也同样需要对MSIL做些操作。先用这个源码来编译得到exe: TestInterfaceCall.cs using System;static class TestInterfaceCall { static void Main(string[] args) { IFoo f = new FooImpl(); f.Method(); Bar b = new Bar(); ((IFoo)b).Method(); // << watch this }}public interface IFoo { void Method();}public class FooImpl : IFoo { public virtual void Method() { Console.WriteLine("FooImpl.Method()"); }}public class Bar { public virtual void AnotherMethod() { Console.WriteLine("Bar.AnotherMethod()"); }} 然后利用ILDASM将其反编译为MSIL: TestInterfaceCall.il // Microsoft (R) .NET Framework IL Disassembler. Version 3.5.30729.1// Copyright (c) Microsoft Corporation. All rights reserved.// Metadata version: v2.0.50727.assembly extern mscorlib{ .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. .ver 2:0:0:0}.assembly TestInterfaceCall{ .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx 63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows. .hash algorithm 0x00008004 .ver 0:0:0:0}.module TestInterfaceCall.exe// MVID: {075D6351-0AF0-459F-A822-40E817B58699}.imagebase 0x00400000.file alignment 0x00000200.stackreserve 0x00100000.subsystem 0x0003 // WINDOWS_CUI.corflags 0x00000001 // ILONLY// Image base: 0x03010000// =============== CLASS MEMBERS DECLARATION ===================.class private abstract auto ansi sealed beforefieldinit TestInterfaceCall extends [mscorlib]System.Object{ .method private hidebysig static void Main(string[] args) cil managed { .entrypoint // Code size 33 (0x21) .maxstack 1 .locals init (class IFoo V_0, class Bar V_1) IL_0000: nop IL_0001: newobj instance void FooImpl::.ctor() IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: callvirt instance void IFoo::Method() IL_000d: nop IL_000e: newobj instance void Bar::.ctor() IL_0013: stloc.1 IL_0014: ldloc.1 IL_0015: castclass IFoo IL_001a: callvirt instance void IFoo::Method() IL_001f: nop IL_0020: ret } // end of method TestInterfaceCall::Main} // end of class TestInterfaceCall.class interface public abstract auto ansi IFoo{ .method public hidebysig newslot abstract virtual instance void Method() cil managed { } // end of method IFoo::Method} // end of class IFoo.class public auto ansi beforefieldinit FooImpl extends [mscorlib]System.Object implements IFoo{ .method public hidebysig newslot virtual instance void Method() cil managed { // Code size 13 (0xd) .maxstack 8 IL_0000: nop IL_0001: ldstr "FooImpl.Method()" IL_0006: call void [mscorlib]System.Console::WriteLine(string) IL_000b: nop IL_000c: ret } // end of method FooImpl::Method .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // Code size 7 (0x7) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: ret } // end of method FooImpl::.ctor} // end of class FooImpl.class public auto ansi beforefieldinit Bar extends [mscorlib]System.Object{ .method public hidebysig newslot virtual instance void AnotherMethod() cil managed { // Code size 13 (0xd) .maxstack 8 IL_0000: nop IL_0001: ldstr "Bar.AnotherMethod()" IL_0006: call void [mscorlib]System.Console::WriteLine(string) IL_000b: nop IL_000c: ret } // end of method Bar::AnotherMethod .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // Code size 7 (0x7) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: ret } // end of method Bar::.ctor} // end of class Bar// =============================================================// *********** DISASSEMBLY COMPLETE ***********************// WARNING: Created Win32 resource file D:\experiment\test\TestInterfaceCall.res 与前一帖一样,这里我们要避免castclass指令干扰测试的结果。把Main()里的IL_0015那行注释掉,然后再用ILASM重新编译为TestInterfaceCall.exe。 运行结果如下: D:\experiment\test>TestInterfaceCall.exeFooImpl.Method()Unhandled Exception: System.EntryPointNotFoundException: Entry point was not found. at IFoo.Method() at TestInterfaceCall.Main(String[] args) 可以看到,这一点上CLR与JVM的行为一致。 =================================================================== CLR 2.0之前是用全局的接口查找表来做接口方法调用的分发(dispatch)的。到2.0之后,CLR改用基于stub的方式来做接口方法调用分发。对接口方法的调用点,JIT一开始会生成一个间接调用指向一个lookup stub,后者又指向一个resolver stub,用于查找实际的方法实现;对同一个被调用对象类型成功调用2次之后会生成特化于那个类型的dispatch stub(是一个monomorphic inline cache),然后记录该stub调用失败的次数,达到100次之后就退化回到非特化的resolver stub。 如果被调用对象没有实现指定的接口,则在调用点初次被调用时,进到resolver stub之后会发现找不到接口方法的具体实现,然后就抛出异常。 =================================================================== 值得注意的是,这两帖里提到的“接口方法调用”都是指正常的、直接的调用,而不是通过反射去做的调用。如果把这帖里的代码例子中第9行的((IFoo)b).Method();改为反射调用: typeof(IFoo).GetMethod("Method").Invoke(b, new object[0]); 则编译和校验都不会出现问题,而运行时抛出的异常会是这样的: [code=""]Unhandled Exception: System.Reflection.TargetException: Object does not match target type. at System.Reflection.RuntimeMethodInfo.CheckConsistency(Object target) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at TestInterfaceCall.Main(String[] args)[/code] 与直接在IL里去调用接口方法不同。这是因为反射本身就是通过托管代码实现的,也就是说在进到虚拟机底层之前,在托管代码里就已经做了很多检查,这些检查发现所要调用的方法与实际提供的被调用对象不匹配,于是在托管代码的层次上就抛出了异常。
转载请注明原文地址: https://www.6miu.com/read-5052107.html

最新回复(0)