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

srm*_*srm 39 .net c#

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

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

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

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

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

Eri*_*ert 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的答案中的链接.


Mat*_*son 14

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

调用虚方法的代码首先确定正在使用的对象的类型.然后它查询类型的方法表以查找被调用的方法.然后代码简单地调用该方法,将对象的引用作为"this"与任何其他参数一起传递.

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

有关方法表的更多详细信息,请参阅2005年5月版MSDN杂志中的"JIT and Run"文章(在撰写本文时,可以从此页面下载为".chm" - 但是你必须要去由于安全限制,在文件属性正确显示之前解锁它.)

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