callvirt .NET指令如何用于接口?
向别人讲解虚拟派发很容易:每个对象都有一个指向表的指针作为其数据的一部分。 class上有N个虚拟方法。 每当调用某个特定的方法时,我都会在对象到达时将其索引,并调用表中的第i个方法。 实现方法X()的每个类将在同一个ith索引中拥有方法X()的代码。
但是,然后我们得到接口。 而接口需要某种扭曲,因为两个实现相同接口的非inheritance类将具有不同索引的虚函数。
我已经search了互联网,并且我可以find关于如何实现接口调度的许多讨论。 有两大类:a)某种散列表查找对象以find正确的分派表b)当对象被转换到接口时,创build一个新的指针指向相同的数据,但指向不同的虚函数表。
但是尽pipe有很多关于如何工作的信息,但是我并没有发现.NET运行时引擎实际上如何实现它。
有没有人知道一个文档,描述了当对象types是一个接口时,在callvirt指令中发生的实际指针algorithm?
CLR中的接口调度是黑魔法。
正如您正确地指出,虚拟方法调度在概念上很容易解释。 事实上,我在这个系列的文章中这样做,在那里我描述了如何以类似C#的语言来实现虚拟方法:
我描述的机制与实际使用的机制非常相似。
接口调度很难描述,而CLR实现它的方式并不明显。 CLR的接口调度机制经过仔细的调整,可以为大多数常见的情况提供高性能,因此CLR团队开发更多关于现实世界的使用模式的知识,这些机制的细节可能会发生变化。
基本上,它在幕后工作的方式是,每个调用站点 – 即调用接口方法的代码中的每个点 – 都有一个小caching,它说:“我认为与此接口插槽关联的方法是。 .. 这里”。 绝大多数时候,caching是正确的; 你很less会用一百万个不同的实现来调用相同的接口方法一百万次。 它通常是一次又一次的重复,连续多次。
如果caching结果是未命中,那么它将回退到保留的散列表,以稍微慢一点的查找。
如果结果是未命中,则分析对象元数据以确定对应于接口插槽的方法。
最终结果是,在给定的调用站点上,如果您总是调用一个映射到特定类方法的接口方法,那么它非常快。 如果你总是为给定的接口方法调用一些类方法,性能是相当不错的。 最糟糕的情况是,在同一个站点上,不要使用相同的接口方法两次调用相同的类方法; 每次都需要最慢的path。
如果你想知道如何在内存中保留缓慢查找表,请参阅Matthew Watson的答案。
因为编译器总是必须有一个实际的对象来调用方法(在运行时),它总是在运行时知道它正在处理的具体types。
调用虚拟方法的代码首先确定正在使用的对象的types。 然后查阅types的方法表来查找被调用的方法。 然后代码简单地调用该方法,将对象的引用作为“this”以及任何其他parameter passing。
我怀疑你感兴趣的关键是代码如何在types的方法表中查找方法的地址。
有关方法表的更多细节在MSDN杂志2005年5月版的“JIT and Run”一文中给出(在撰写本文时,可以从本页下载为“.chm”),但是你必须去到文件的属性解锁之前它会显示正常,由于安全限制。)
关于查询的完成方式,还是有一点手头的意思,但是它提供了很多其他的细节。