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

13 篇文章 0 订阅
7 篇文章 0 订阅
作者: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.exe
FooImpl.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里去调用接口方法不同。这是因为反射本身就是通过托管代码实现的,也就是说在进到虚拟机底层之前,在托管代码里就已经做了很多检查,这些检查发现所要调用的方法与实际提供的被调用对象不匹配,于是在托管代码的层次上就抛出了异常。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值