聊聊 C# 中的多态底层 (虚方法调用) 是怎么玩的

最近在看 C++ 的虚方法调用实现原理,大概就是说在 class 的首位置存放着一个指向 vtable array 指针数组 的指针,而 vtable array 中的每一个指针元素指向的就是各自的 虚方法,实现方式很有意思,哈哈,现在我很好奇 C# 中如何实现的。

一:C# 中的多态玩法

1. 一个简单的 C# 例子

为了方便说明,我就定义一个 Person 类和一个 Chinese 类,详细代码如下:

internal class Program{static void Main(string[] args){Person person = new Chinese();person.SayHello();Console.ReadLine();}}public class Person{public virtual void SayHello(){Console.WriteLine("sayhello");}}public class Chinese: Person{public override void SayHello(){Console.WriteLine("chinese");}}
}

2. 汇编代码分析

接下来用 windbg 在 person.SayHello() 处下一个断点,观察一下它的反汇编代码:

D:\net6\ConsoleApplication2\ConsoleApp1\Program.cs @ 9:
05cf21b3 b93c5dce05      mov     ecx,5CE5D3Ch (MT: ConsoleApp1.Chinese)
05cf21b8 e8030f89fa      call    005830c0 (JitHelp: CORINFO_HELP_NEWSFAST)
05cf21bd 8945f4          mov     dword ptr [ebp-0Ch],eax
05cf21c0 8b4df4          mov     ecx,dword ptr [ebp-0Ch]
05cf21c3 e820fbffff      call    05cf1ce8 (ConsoleApp1.Chinese..ctor(), mdToken: 0600000A)
05cf21c8 8b4df4          mov     ecx,dword ptr [ebp-0Ch]
05cf21cb 894df8          mov     dword ptr [ebp-8],ecxD:\net6\ConsoleApplication2\ConsoleApp1\Program.cs @ 11:
>>> 05cf21ce 8b4df8          mov     ecx,dword ptr [ebp-8]
05cf21d1 8b45f8          mov     eax,dword ptr [ebp-8]
05cf21d4 8b00            mov     eax,dword ptr [eax]
05cf21d6 8b4028          mov     eax,dword ptr [eax+28h]
05cf21d9 ff5010          call    dword ptr [eax+10h]
05cf21dc 90              nop

从汇编代码看,逻辑非常清晰,大体步骤如下:

  1. eax,dword ptr [ebp-8]

从栈上(ebp-8)处获取 person 在堆上的首地址,如果不相信的话,可以用 !do 027ea88c 试试看。

0:000> dp ebp-8 L1
0057f300  027ea88c0:000> !do 027ea88c
Name:        ConsoleApp1.Chinese
MethodTable: 05ce5d3c
EEClass:     05cd3380
Size:        12(0xc) bytes
File:        D:\net6\ConsoleApplication2\ConsoleApp1\bin\x86\Debug\net6.0\ConsoleApp1.dll
Fields:
None
  1. eax,dword ptr [eax]

如果大家了解 实例 在堆上的内存布局的话,应该知道,这个首地址存放的就是 methodtable 指针,我们可以用 !dumpmt 05ce5d3c 来验证下。

0:000> dp 027ea88c L1
027ea88c  05ce5d3c0:000> !dumpmt 05ce5d3c
EEClass:         05cd3380
Module:          05addb14
Name:            ConsoleApp1.Chinese
mdToken:         02000007
File:            D:\net6\ConsoleApplication2\ConsoleApp1\bin\x86\Debug\net6.0\ConsoleApp1.dll
BaseSize:        0xc
ComponentSize:   0x0
DynamicStatics:  false
ContainsPointers false
Slots in VTable: 6
Number of IFaces in IFaceMap: 0
  1. eax,dword ptr [eax+28h]

那这句话是什么意思呢?如果你了解 CoreCLR 的话,你应该知道 methedtable 是由一个 class MethodTable 类来承载的,所以它取了 methodtable 偏移 0x28  位置的一个字段,那这个偏移字段是什么呢?我们先用 dt 把 methodtable 结构给导出来。

0:000> dt 05ce5d3c MethodTable
coreclr!MethodTable=7ad96bc8 s_pMethodDataCache : 0x00639ec8 MethodDataCache=7ad96bc4 s_fUseParentMethodData : 0n1=7ad96bcc s_fUseMethodDataCache : 0n1+0x000 m_dwFlags        : 0xc+0x004 m_BaseSize       : 0x74088+0x008 m_wFlags2        : 5+0x00a m_wToken         : 0+0x00c m_wNumVirtuals   : 0x5ccc+0x00e m_wNumInterfaces : 0x5ce+0x010 m_pParentMethodTable : IndirectPointer<MethodTable *>+0x014 m_pLoaderModule  : PlainPointer<Module *>+0x018 m_pWriteableData : PlainPointer<MethodTableWriteableData *>+0x01c m_pEEClass       : PlainPointer<EEClass *>+0x01c m_pCanonMT       : PlainPointer<unsigned long>+0x020 m_pPerInstInfo   : PlainPointer<PlainPointer<Dictionary *> *>+0x020 m_ElementTypeHnd : 0+0x020 m_pMultipurposeSlot1 : 0+0x024 m_pInterfaceMap  : PlainPointer<InterfaceInfo_t *>+0x024 m_pMultipurposeSlot2 : 0x5ce5d68=7ad04c78 c_DispatchMapSlotOffsets : [0]  " $ (System.Private.CoreLib.dll"=7ad04c70 c_NonVirtualSlotsOffsets : [0]  " $ ($((, $ (System.Private.CoreLib.dll"=7ad04c60 c_ModuleOverrideOffsets : [0]  " $ ($((,$((,(,,0 $ ($((, $ (System.Private.CoreLib.dll"=7ad12838 c_OptionalMembersStartOffsets : [0]  "(((((((,(((,(,,0(((,(,,0(,,0,004"

从 methodtable 的布局图来看, eax+28hm_pMultipurposeSlot2 结构的第二个字段了,因为第一个字段是 虚方法表指针,如果要验证的话,也很简单,用 !dumpmt -md 05ce5d3c 把所有的方法给导出来,然后结合 dp 05ce5d3c 看下 0x5ce5d68 之后是不是许多的方法。

0:000> !dumpmt -md 05ce5d3c
EEClass:         05cd3380
Module:          05addb14
Name:            ConsoleApp1.Chinese
mdToken:         02000007
File:            D:\net6\ConsoleApplication2\ConsoleApp1\bin\x86\Debug\net6.0\ConsoleApp1.dll
BaseSize:        0xc
ComponentSize:   0x0
DynamicStatics:  false
ContainsPointers false
Slots in VTable: 6
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc TableEntry MethodDe    JIT Name
02610028 02605568   NONE System.Object.Finalize()
02610030 02605574   NONE System.Object.ToString()
02610038 02605580   NONE System.Object.Equals(System.Object)
02610050 026055ac   NONE System.Object.GetHashCode()
05CF1CE0 05ce5d24   NONE ConsoleApp1.Chinese.SayHello()
05CF1CE8 05ce5d30    JIT ConsoleApp1.Chinese..ctor()
0:000> dp 05ce5d3c L10
05ce5d3c  00000200 0000000c 00074088 00000005
05ce5d4c  05ce5ccc 05addb14 05ce5d7c 05cd3380
05ce5d5c  05cf1ce8 00000000 05ce5d68 02610028
05ce5d6c  02610030 02610038 02610050 05cf1ce0

仔细看输出,上面的 05ce5d68 后面的 02610028 就是 System.Object.Finalize() 方法,02610030 对应着 System.Object.ToString() 方法。

  1. call dword ptr [eax+10h]

有了前面的基础,这句话就好理解了,它是从 m_pMultipurposeSlot2 结构中找 SayHello 所在的单元指针位置,然后做 call 调用。

0:000> !U 05cf1ce0
Unmanaged code
05cf1ce0 e88f9dde74      call    coreclr!PrecodeFixupThunk (7aadba74)
05cf1ce5 5e              pop     esi
05cf1ce6 0001            add     byte ptr [ecx],al
05cf1ce8 e913050000      jmp     05cf2200
05cf1ced 5f              pop     edi
05cf1cee 0300            add     eax,dword ptr [eax]
05cf1cf0 245d            and     al,5Dh
05cf1cf2 ce              into
05cf1cf3 0500000000      add     eax,0
05cf1cf8 0000            add     byte ptr [eax],al

从汇编看,它还是一段 桩代码,言外之意就是该方法没有被 JIT 编译,如果编译完了,这里的 05CF1CE0 05ce5d24 NONE ConsoleApp1.Chinese.SayHello()  的 Entry (05CF1CE0) 也会被同步修改,验证一下很简单,我们继续 go 代码让其编译完成,然后再 dumpmt 。

0:008> !dumpmt -md 05ce5d3c
EEClass:         05cd3380
Module:          05addb14
Name:            ConsoleApp1.Chinese
mdToken:         02000007
File:            D:\net6\ConsoleApplication2\ConsoleApp1\bin\x86\Debug\net6.0\ConsoleApp1.dll
BaseSize:        0xc
ComponentSize:   0x0
DynamicStatics:  false
ContainsPointers false
Slots in VTable: 6
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc TableEntry MethodDe    JIT Name
02610028 02605568   NONE System.Object.Finalize()
02610030 02605574   NONE System.Object.ToString()
02610038 02605580   NONE System.Object.Equals(System.Object)
02610050 026055ac   NONE System.Object.GetHashCode()
05CF2270 05ce5d24    JIT ConsoleApp1.Chinese.SayHello()
05CF1CE8 05ce5d30    JIT ConsoleApp1.Chinese..ctor()0:008> dp 05ce5d3c L10
05ce5d3c  00000200 0000000c 00074088 00000005
05ce5d4c  05ce5ccc 05addb14 05ce5d7c 05cd3380
05ce5d5c  05cf1ce8 00000000 05ce5d68 02610028
05ce5d6c  02610030 02610038 02610050 05cf2270

此时可以看到它由 05cf1ce0 变成了 05cf2270, 这个就是 JIT 编译后的方法代码,我们用 !U 反编译下。

0:008> !U 05cf2270
Normal JIT generated code
ConsoleApp1.Chinese.SayHello()
ilAddr is 05E720D5 pImport is 008F6E88
Begin 05CF2270, size 27D:\net6\ConsoleApplication2\ConsoleApp1\Program.cs @ 28:
>>> 05cf2270 55              push    ebp
05cf2271 8bec            mov     ebp,esp
05cf2273 50              push    eax
05cf2274 894dfc          mov     dword ptr [ebp-4],ecx
05cf2277 833d74dcad0500  cmp     dword ptr ds:[5ADDC74h],0
05cf227e 7405            je      05cf2285
05cf2280 e8cb2bf174      call    coreclr!JIT_DbgIsJustMyCode (7ac04e50)
05cf2285 90              nopD:\net6\ConsoleApplication2\ConsoleApp1\Program.cs @ 29:
05cf2286 8b0d74207e04    mov     ecx,dword ptr ds:[47E2074h] ("chinese")
05cf228c e8dffbffff      call    05cf1e70
05cf2291 90              nopD:\net6\ConsoleApplication2\ConsoleApp1\Program.cs @ 30:
05cf2292 90              nop
05cf2293 8be5            mov     esp,ebp
05cf2295 5d              pop     ebp
05cf2296 c3              ret

终于这就是多态下的 ConsoleApp1.Chinese.SayHello 方法啦。

3. 总结

本质上来说,CoreCLR 也是 C++ 写的,所以也逃不过用 虚表 来实现多态的玩法, 不过玩法也稍微复杂了一些,希望本篇对大家有帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/286700.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

sublime text 3 安装、添加命令行启动、汉化、注册码

1. 安装sublime&#xff1a; 下载&#xff1a;http://www.sublimetext.com/3 添加命令行启动&#xff1a;设置环境变量->计算机->右键属性->高级系统设置->环境变量->系统变量->Path->编辑&#xff0c;加入";安装路径"(如&#xff1a;;D:\Progr…

MVC基础知识-View

public ActionResult Index() {ViewBag.Message "Modify this template to jump-start your ASP.NET MVC application.";//展现到视图中数据//~/Views/Home/Index.cshtmlreturn View(); //展现指定的视图&#xff0c;当没有指定视图名称时&#xff0c;默认是指向根目…

Android之设置当前app为默认浏览器研究

1 需求 把当前app设置为默认浏览器 2 主要思路 分析竞品,我们主要通过2种方式设置app为默认浏览器 直接跳 手机“设置默认应用”界面来让用户选择 默认浏览器为哪个app打开一个链接,拉起手机所有浏览器,让用户去选择哪个浏览器,并且只有点击“始终”才会生效,仅仅点击“…

Linux下的用户和组

2019独角兽企业重金招聘Python工程师标准>>> 用户和组 GNU/Linux 通过用户和用户组实现访问控制 —— 包括对文件访问、设备使用的控制。Linux 默认的访问控制机制相对简单直接&#xff0c;不过还有一些更加高级的机制&#xff0c;包括 ACL 和 LDAP Authentication.…

漫画C语言 做个聊天软件你不懂也得懂

学完C语言做不出东西&#xff1f;不存在的&#xff0c;咱们做一个最“隐私”的聊天器&#xff0c;就俩人&#xff0c;你和我。咱们聊天的信息你知我知没别人知。 对了&#xff0c;本文评论区点赞、收藏抽奖。 社区也有抽奖&#xff0c;本周社区抽奖帖子 &#xff1a;https://b…

【Microstation】第一章:Microstation三维模型构建概述

MicroStation 是国际上和AutoCAD齐名的二维和三维CAD设计软件&#xff0c;第一个版本由Bentley兄弟在1986年开发完成。其专用格式是DGN&#xff0c;并兼容AutoCAD的DWG/DXF等格式。 MicroStation是Bentley 工程软件系统有限公司在建筑、土木工程、交通运输、加工工厂、离散制造…

libgdx游戏引擎开发笔记(十)SuperJumper游戏例子的讲解(篇四)---- 主游戏界面内部框架编写...

上一讲&#xff0c;我们已经实现了点击play进入游戏界面但仅仅是个黑屏 今天&#xff0c;我们就试着编写代码让它出现游戏的一些简单场景。还是在上一讲的代码基础上&#xff0c;我们创建两个类&#xff1a;World 和 WorldRenderer 1.Word类&#xff1a; 12345678910111213141…

看看《System.CommandLine》

记得之前出过几篇.net tool工具的文章&#xff0c;当时的做法是所有工具的语法分析全部自己解释&#xff0c;自己执行&#xff0c;语法的解释占了大部分时间&#xff0c;反而工具的功能被弱化了。其实微软有一个CommandLine框架在缓慢的发展着&#xff0c;至今都没有正式发布&a…

Sublime Text 3 import Anaconda 无法正常补全模块名解决办法

Sublime Text 3 Anaconda配置 在安装Sublime Text3之后我们总会安装一些插件&#xff0c;比如Python的Anaconda自动补全插件。但是&#xff0c;装好之后发现import 时无法像别的IDE里面那样自动补全模块名&#xff0c;就像图中一样&#xff1a; 解决办法在Sublime Text的git…

hdu - 2586 How far away ?(最短路共同祖先问题)

题目链接&#xff1a;http://acm.hdu.edu.cn/showproblem.php?pid2586 最近公共祖先问题~~LAC离散算法 题目大意&#xff1a;一个村子里有n个房子&#xff0c;这n个房子用n-1条路连接起来&#xff0c;接下了有m次询问&#xff0c;每次询问两个房子a,b之间的距离是多少。 很明显…

Android之添加固定图标到桌面

我的QQ群 1 需求 Android之添加固定图标到桌面 2 部分实现 在AndroidManifest.xml里面添加如下权限 <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" /><uses-permission android:name="com.android.launcher.perm…

想做程序员?不同方向入门路线全解

学习计算机编程有很多方向如果你没有一个正确的路线&#xff0c;那么就会&#xff1a; 就会跟上面所说的那样&#xff0c;被迫成为一个全栈&#xff0c;这是比较尴尬的。 若你想比较准确的针对某个方向学习&#xff0c;那就继续往下看吧。 一、程序员分为哪几个方向 随着…

【转】OpenGL超级宝典笔记——纹理映射Mipmap

原文地址 http://my.oschina.net/sweetdark/blog/177812 , 感谢作者&#xff0c;若非法转载请联系本人。 目录[-] MipmappingMipmap过滤构建Mip层Mipmaps 硬件生成LOD&#xff08;多细节层次&#xff09;偏好纹理对象管理多个纹理常驻纹理纹理优先级回顾Mipmapping Mipmap是一个…

【Microstation】第二章:Microstation三维建模基础知识

本章的主要内容包括模型的显示样式(线框、光滑)、三维定位(V、T、S、F)、Microstation常见的坐标系统(世界坐标系、ACS辅助坐标系、精确绘图坐标系、)和Microstation的工作区域(2D和3D)。 一、显示样式 二、三维定位 三维定位在Microstation中显得尤为重要,常见…

xtrabackup对MySQL数据库的备份及恢复教程

xtrabackup xtrabackup 是 percona 的一个开源项目&#xff0c;可以热备份innodb &#xff0c;XtraDB,和MyISAM&#xff08;会锁表&#xff09;。对MyISAM存储引擎会锁表&#xff0c;也是很郁闷的因为线上使用的是Innodb和MyISAM两种存储引擎&#xff0c;比较 头疼&#xff01;…

实现 EF Core 6 自定义查询标记

前言在《EF Core使用Simple Logging输出日志》中&#xff0c;我们介绍了查询标记 TagWith&#xff0c;它可以帮助我们快速定位到需要的日志&#xff1a;而在 .NET 6 中&#xff0c;新增了另外一个查询标记 TagWithCallSite&#xff0c;它可以标记出代码的位置&#xff1a;var u…

LeetCode: 14. Longest Common Prefix

Write a function to find the longest common prefix string amongst an array of strings. 大意就是&#xff0c;写一个函数可以找到一个数组字符串中的最长前缀。 分析&#xff1a; 最长前缀的最大值为数组字符串中长度最短的字符&#xff0c;由最短字符串由后向前递减可以得…

jQuery选择器和选取方法

我们已经使用了带有简单Css选择器的jQuery选取函数:$()。现在是时候深入了解jQuery选择器语法&#xff0c;以及一些提取和扩充选中元素集的方法了。 一、jQuery选择器 在CSS3选择器标淮草案定义的选择器语法中&#xff0c;jQuery支持相当完整的一套子集&#xff0c;同时还添加了…

0运维?微信小程序云开发增删查改【05】

在创建小程序时&#xff0c;选择云开发&#xff1a; 随后进入项目之后&#xff0c;此时整个目录如下&#xff1a; 此时我们如图目录即可找到首页位置&#xff1a; 接着咱们清除 index.wxml 代码内容&#xff1a; 在 index.wxml 中加入如下代码&#xff1a; <view> …

Android之解决卸载app后再次安装提示room数据库错误

1、问题 目前只有一个google手机之前安装了app,里面有room写的数据库&#xff0c;后面把app卸载了&#xff0c;再次安装新的app(修改了数据库里面的字段)&#xff0c;启动奔溃。 2、分析 提示数据库错误&#xff0c;很明显就像以前的app里面的数据库没有删除一样&#xff0c;…