调用virt .NET指令如何用于接口

本文关键字:用于 接口 何用于 virt NET 指令 调用 | 更新日期: 2023-09-27 18:32:44

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

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

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

但是,尽管有很多关于

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

有谁知道一个文档描述了当对象类型是接口时在 callvirt 指令中发生的实际指针算术?

调用virt .NET指令如何用于接口

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 答案中的链接。

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

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

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

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

关于查找的确切方式仍然有点手摇晃晃,但它确实提供了很多其他细节。