callvirt .NET指令如何用于接口?

时间:2013-09-25 19:54:26

标签: c# .net

向某人解释虚拟调度很容易:每个对象都有一个指向表的指针作为其数据的一部分。该类有N个虚拟方法。每次调用特定方法时,我都会在对象到达时对其进行索引并调用表中的第i个方法。实现方法X()的每个类都将在同一个索引中包含方法X()的代码。

然后我们得到接口。并且接口需要某种类型的扭曲,因为两个实现相同接口的非继承类将在表的不同索引中具有虚函数。

我已经在互联网上搜索过,我可以找到很多关于如何实现接口调度的讨论。有两大类: a)某种哈希表在对象上查找以找到正确的分派表 b)当对象被强制转换为接口时,会创建一个指向相同数据但指向不同vtable的新指针。

尽管有很多关于如何工作的信息,但我找不到.NET运行时引擎如何实际实现它。

当对象类型是接口时,是否有人知道描述callvirt指令中发生的实际指针算法的文档?

2 个答案:

答案 0 :(得分:37)

CLR中的接口调度是黑魔法。

正如您所正确指出的那样,虚拟方法调度在概念上很容易解释。事实上,我在这一系列文章中这样做,在那里我描述了如何使用缺少它们的类似C#的语言实现虚拟方法:

http://blogs.msdn.com/b/ericlippert/archive/2011/03/17/implementing-the-virtual-method-pattern-in-c-part-one.aspx

我描述的机制与实际使用的机制非常相似。

接口调度更难描述,CLR实现它的方式并不明显。已经仔细调整了用于界面调度的CLR机制,以便为最常见的情况提供高性能,因此这些机制的细节可能会随着CLR团队开发更多关于实际使用模式的知识而发生变化。

基本上它在幕后工作的方式是每个调用站点 - 也就是代码中调用接口方法的每个点 - 有一个小缓存说“我认为与此接口相关的方法槽是......这里“。在绝大多数情况下,缓存是正确的;你很少用百万种不同的实现来调用相同的接口方法一百万次。它通常是一次又一次地执行相同的实现,连续多次。

如果缓存结果是未命中,那么它将回退到维护的哈希表,以进行稍慢的查找。

如果 结果是未命中,则分析对象元数据以确定哪个方法对应于接口槽。

实际效果是,在给定的调用站点,如果您始终调用映射到特定类方法的接口方法,则非常快。如果你总是为给定的接口方法调用少数类方法之一,那么性能非常好。最糟糕的事情是永远不要在同一个站点使用相同的接口方法两次调用相同的类方法;每次都采用最慢的路径。

如果您想知道如何在内存中维护慢查找的表,请参阅Matthew Watson的答案中的链接。

答案 1 :(得分:14)

因为编译器总是必须有一个实际的对象来调用该方法(在运行时),所以它总是在运行时知道它正在处理的具体类型。

调用虚方法的代码首先确定所使用对象的类型。然后它查询类型的方法表以查找被调用的方法。然后代码简单地调用该方法,将对象的引用作为“this”以及任何其他参数传递。

我怀疑你感兴趣的关键是代码如何在类型的方法表中查找方法的地址。

有关方法表的更多详细信息,请参阅2005年5月版MSDN杂志的“JIT和运行”一文(在撰写本文时,可以从this page下载为“.chm” - 但由于安全限制,您必须转到文件的属性才能解锁,然后才能正常显示。)

关于查找是如何完成的,它仍然有点波动,但它确实提供了很多其他细节。