关于多态不多解释了,在运行时决定和调用具体的实现,是面向对象的基础 设计模式的基础.
准备把继承多态和接口多态分开,因为从CLR实现的角度继承多态相比于接口多态要简单得多,也更容易理解,本篇只讨论继承多态, .NET Framework 2.0 和 4.0 这两个版本在实现上稍微有点区别(这里先忽略方法Jit编译的过程,只关注实现的方式).

废话不多,先看代码: C# Polymorphism01.cs

using System;using System.Runtime.CompilerServices;public class Program{   public static void Main(string[] args)   {        Console.WriteLine("Polymorphism01 demo");      BaseClass bc = new BaseClass();     BaseClass bc1 = new ChlidClass();       BaseClass bc2 = new BrotherClass();     BaseClass bc3 = new DerivedOfBrotherClass();        BrotherClass bc4 = new DerivedOfBrotherClass();

     bc.VirtualFun1();        bc1.VirtualFun1();       bc2.VirtualFun1();       bc3.VirtualFun1();       bc3.VirtualFun2();       bc4.VirtualFun3();

     Console.ReadLine();  }}public class BaseClass{   [MethodImpl(MethodImplOptions.NoInlining)]   public virtual void VirtualFun1()    {        Console.WriteLine("BaseClass VirtualFun1");    }    [MethodImpl(MethodImplOptions.NoInlining)]   public virtual void VirtualFun2()    {        Console.WriteLine("BaseClass VirtualFun2");    }}public class ChlidClass : BaseClass{  [MethodImpl(MethodImplOptions.NoInlining)]   public override void VirtualFun1()   {        Console.WriteLine("ChlidClass VirtualFun1");   }    [MethodImpl(MethodImplOptions.NoInlining)]   public override void VirtualFun2()   {        Console.WriteLine("ChlidClass VirtualFun2");   }}public class BrotherClass : BaseClass{    [MethodImpl(MethodImplOptions.NoInlining)]   public override void VirtualFun1()   {        Console.WriteLine("BrotherClass VirtualFun1"); }    [MethodImpl(MethodImplOptions.NoInlining)]   public override void VirtualFun2()   {        Console.WriteLine("BrotherClass VirtualFun2"); }

 [MethodImpl(MethodImplOptions.NoInlining)]   public virtual void VirtualFun3()    {        Console.WriteLine("BrotherClass VirtualFun3"); }}public class DerivedOfBrotherClass : BrotherClass{    [MethodImpl(MethodImplOptions.NoInlining)]   public override void VirtualFun1()   {        Console.WriteLine("DerivedOfBrotherClass VirtualFun1");    }    [MethodImpl(MethodImplOptions.NoInlining)]   public override void VirtualFun2()   {        Console.WriteLine("DerivedOfBrotherClass VirtualFun2");    }

 [MethodImpl(MethodImplOptions.NoInlining)]   public override void VirtualFun3()   {        Console.WriteLine("DerivedOfBrotherClass VirtualFun3");    }}
  • 编译代码 先用 .Net Framework 2.0 编译:

    12
    %windir%\Microsoft.NET\Framework\v2.0.50727\csc.exe /debug /target:exe /out:e:\temp\Polymorphism01_2.0.exe e:\temp\Polymorphism01.cspause
  • 运行 Polymorphism01_2.0.exe

  • 启动windbg 附加进程 加载SOS

  • 查找对应的模块:
    !Name2EE *!Polymorphism01_2.0.exe

0:004> !Name2EE *!Polymorphism01_2.0.exeModule: 790c1000 (mscorlib.dll)--------------------------------------Module: 00af2c5c (Polymorphism01_2.0.exe)

根据模块查找方法表:
!DumpModule -mt 00af2c5c

  • 先分别看下 BaseClass BrotherClass DerivedOfBrotherClass 这3个继承关系类的方法表(MethodTable)

可以看到第一个虚方法(ToString)的入口都是在方法表偏移28h的位置,其顺序是先父类,再子类,这样的安排让所有同一个家族(继承关系)的类型继承虚方法的顺序是一样的,并且偏移量是一样的,所有的类型(除了接口类型)的父类都是(或者间接是)System.Object,所以前4个虚方法肯定是Object里的4个虚方法(ToString Equals GetHashCode Finalize)

通过Program 的方法表(MethodTable)找到Main方法的入口地址:
!DumpMT -md 00af302c

0:004> !DumpMT -md 00af302cEEClass: 00af12f4Module: 00af2c5cName: ProgrammdToken: 02000002  (E:\temp\Polymorphism01_2.0.exe)BaseSize: 0xcComponentSize: 0x0Number of IFaces in IFaceMap: 0Slots in VTable: 6--------------------------------------MethodDesc TableEntry MethodDesc      JIT Name79286aa0   79104960   PreJIT System.Object.ToString()79286ac0   79104968   PreJIT System.Object.Equals(System.Object)79286b30   79104998   PreJIT System.Object.GetHashCode()792f76d0   791049bc   PreJIT System.Object.Finalize()00afc015   00af3024     NONE Program..ctor()01010070   00af3018      JIT Program.Main(System.String[])

Main方法已经Jit编译,看看被编译成啥样子:
!u 01010070

  • 这里最重要的几行:

    01010139 8b4df8          mov     ecx,dword ptr [ebp-8]   // 这里是BaseClass实例对象的地址 放到 ecx寄存器,Jit采用类似fastcall的调用协定,前2个不大于4字节的参数用 ecx edx来传递,而实例方法的调用第一个参数是隐含的this指针(托管对象在托管堆上的地址),如果是静态方法就不需要传this pointer了0101013c 8b01            mov     eax,dword ptr [ecx]    // 托管堆上的对象(值类型装箱后也是一样)第一个4字节(64位8字节)是对象的方法表地址(MethodTable),这里是把方法表(MethodTable)地址赋给eax寄存器0101013e ff5038          call    dword ptr [eax+38h]    // 这里就是实际的方法调用 上面说了 第一个虚方法在方法表的偏移28h位置,前4个是Object里的4个虚方法,所以 VirtualFun1 的入口在方法表地址(MT) + 28h + 4×4字节 也就是偏移38h的位置01010141 90              nop01010142 8b4df4          mov     ecx,dword ptr [ebp-0Ch]    // 这里是 ChlidClass的对象地址赋给ecx01010145 8b01            mov     eax,dword ptr [ecx]    // 同样ChlidClass的方法表地址赋给eax01010147 ff5038          call    dword ptr [eax+38h]    // 调用ChlidClass方法表偏移38h的方法,也是VirtualFun1 方法0101014a 90              nop0101014b 8b4df0          mov     ecx,dword ptr [ebp-10h]    // BrotherClass的对象地址赋给ecx0101014e 8b01            mov     eax,dword ptr [ecx]    // BrotherClass方法表地址赋给eax01010150 ff5038          call    dword ptr [eax+38h]    // 调用BrotherClass方法表偏移38h的方法,也是VirtualFun1 方法01010153 90              nop01010154 8b4dec          mov     ecx,dword ptr [ebp-14h]    // DerivedOfBrotherClass的对象地址赋给ecx01010157 8b01            mov     eax,dword ptr [ecx]    // DerivedOfBrotherClass方法表地址赋给eax01010159 ff5038          call    dword ptr [eax+38h]     // 调用DerivedOfBrotherClass方法表偏移38h的方法,也是VirtualFun1 方法0101015c 90              nop0101015d 8b4dec          mov     ecx,dword ptr [ebp-14h]    // 还是DerivedOfBrotherClass对象地址01010160 8b01            mov     eax,dword ptr [ecx]    // DerivedOfBrotherClass的方法表赋给eax01010162 ff503c          call    dword ptr [eax+3Ch]    // 这次偏移不一样了,第6个方法 VirtualFun2 (28h+5×4字节)01010165 90              nop01010166 8b4de8          mov     ecx,dword ptr [ebp-18h]    // 还是DerivedOfBrotherClass对象地址01010169 8b01            mov     eax,dword ptr [ecx]    // DerivedOfBrotherClass的方法表赋给eax0101016b ff5040          call    dword ptr [eax+40h]    // 这次偏移又不一样了,第7个方法 VirtualFun3 (28h+6×4字节)
  • 可以看到 继承多态在CLR运行时的实现是通过方法表的偏移 间接调用的,而方法表内继承虚方法的构建顺序是先父类再子类,由于.NET是单一继承,这样就确保了在同一家族的同一虚方法的偏移量是一样的.

  • 接下来用Framework 4.0 编译下源码,4.0 和2.0相比 在实现上多了一层间接寻址,但思路是一样的

%windir%\Microsoft.NET\Framework\v4.0.30319\csc.exe /debug /target:exe /out:e:\temp\Polymorphism01_4.0.exe e:\temp\Polymorphism01.cspause
  • 运行 Polymorphism01_4.0.exe

  • 启动windbg 附加进程 加载SOS (这里要加载对于4.0的sos.dll)

  • 直接查找Main方法:
    !Name2EE Polymorphism01_4.0.exe Program.Main

0:004> !Name2EE Polymorphism01_4.0.exe Program.MainModule:      00b32ea4Assembly:    Polymorphism01_4.0.exeToken:       06000001MethodDesc:  00b33838Name:        Program.Main(System.String[])JITTED Code Address: 033a0070
  • 看Main方法的区别:!u 033a0070
    这里只截取最重要的一段,调用构造器和其他的部分都先忽略

    ...e:\temp\Polymorphism01.cs @ 16:033a0139 8b4df8          mov     ecx,dword ptr [ebp-8]  // 这个还是一样BaseClass对象的地址赋给ecx033a013c 8b01            mov     eax,dword ptr [ecx]    // 还是对象的第一个4字节是方法表地址 赋给eax033a013e 8b4028          mov     eax,dword ptr [eax+28h]    // 这里是和2.0的区别 所有继承的虚方法的起始地址保存在方法表偏移28h的位置,也就是偏移量不是从方法表地址开始算了033a0141 ff5010          call    dword ptr [eax+10h]    // 这里的方式一样的 eax是虚方法的起始位置了,前4个是Object的4个虚方法,偏移10h是第5个方法 VirtualFun1033a0144 90              nope:\temp\Polymorphism01.cs @ 17:033a0145 8b4df4          mov     ecx,dword ptr [ebp-0Ch]    // ChlidClass对象地址赋给ecx033a0148 8b01            mov     eax,dword ptr [ecx]    // ChlidClass方法表地址赋给eax033a014a 8b4028          mov     eax,dword ptr [eax+28h]    // 虚表入口地址赋给eax033a014d ff5010          call    dword ptr [eax+10h]    //还是偏移到第5个方法 VirtualFun1033a0150 90              nope:\temp\Polymorphism01.cs @ 18:033a0151 8b4df0          mov     ecx,dword ptr [ebp-10h]    // BrotherClass对象地址赋给ecx033a0154 8b01            mov     eax,dword ptr [ecx]     // BrotherClass方法表地址赋给eax033a0156 8b4028          mov     eax,dword ptr [eax+28h]    // 虚表入口地址赋给eax033a0159 ff5010          call    dword ptr [eax+10h]    //还是偏移到第5个方法 VirtualFun1033a015c 90              nope:\temp\Polymorphism01.cs @ 19:033a015d 8b4dec          mov     ecx,dword ptr [ebp-14h]    // DerivedOfBrotherClass对象地址赋给ecx033a0160 8b01            mov     eax,dword ptr [ecx]    // DerivedOfBrotherClass方法表地址赋给eax033a0162 8b4028          mov     eax,dword ptr [eax+28h]    // 虚表入口地址赋给eax033a0165 ff5010          call    dword ptr [eax+10h]    //还是偏移到第5个方法 VirtualFun1033a0168 90              nope:\temp\Polymorphism01.cs @ 20:033a0169 8b4dec          mov     ecx,dword ptr [ebp-14h]    // 上面同一个对象033a016c 8b01            mov     eax,dword ptr [ecx]033a016e 8b4028          mov     eax,dword ptr [eax+28h]033a0171 ff5014          call    dword ptr [eax+14h]    // 这里比上面的调用多偏移了4个字节 也就是第6个方法 VirtualFun2033a0174 90              nope:\temp\Polymorphism01.cs @ 21:033a0175 8b4de8          mov     ecx,dword ptr [ebp-18h]    // 和上面不是同一个对象地址,但是是实例化同样类型的对象033a0178 8b01            mov     eax,dword ptr [ecx]033a017a 8b4028          mov     eax,dword ptr [eax+28h]033a017d ff5018          call    dword ptr [eax+18h]     // 这里比上面的调用再多偏移了4个字节 也就是第7个方法 VirtualFun3033a0180 90              nop...
  • .NET 4.0 比2.0 多了一次间接寻址,就是先偏移到虚表的入口,再从这个入口开始偏移到相应的方法,这样的好处(个人觉得)虚表的存储位置可以更灵活 如果方法表(MT)包含多个可变长结构也没问题 只要入口地址保存在偏移28h的位置即可

参考文档:

https://www.microsoft.com/china/MSDN/library/netFramework/netframework/JITCompiler.mspx?mfr=true
http://www.codeproject.com/Articles/20481/NET-Type-Internals-From-a-Microsoft-CLR-Perspecti
http://blogs.microsoft.co.il/sasha/2012/03/15/virtual-method-dispatch-and-object-layout-changes-in-clr-40/
http://www.cnblogs.com/BlueTzar/articles/884694.html

相关文章:

  • CLR运行时细节 - Method Descriptor

原文地址:https://espider.github.io/CLR/inheritance-polymorphism/


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

赞赏

人赞赏

CLR运行时细节 - 继承多态的实现相关推荐

  1. CLR运行时细节 - Method Descriptor

    方法描述符:MethodDesc 运行时用来描述类型的托管方法,它保存在方法描述桶(MethodDescChunk)内; 方法描述符保存了方法在运行时的一些重要信息: 是否JIT编译; 是否有方法表槽 ...

  2. .Net CLR运行时是如何编译函数的

    首先了解几个概念和内存结构 一: 1.函数描述符MethodDesc,包含了函数是否被编译标志,函数当前在函数描述符块的索引,以及函数在函数表的索引,以及Token 2.函数描述符块MethodDes ...

  3. ABAP SOAMANAGER暴露的函数function module,以web service方式执行的运行时细节

    要获取更多Jerry的原创文章,请关注公众号"汪子熙":

  4. 快速搭建本地 .NET Core 运行时调试环境

    需要的软件环境: Oracle VM VirtualBox CentOS 7 llvm lldb 3.6.0 (3.5.0我试过 dumpobj时候一直报无效参数 Invalid parameter ...

  5. java多态编译_关于java:编译时多态是否是这样的运行时多态?

    通过研究,我了解到: 重载,运算符重载和重载是我们所说的多态. 多态性意味着同一实体有时表现不同 例如: 参数 add(12, 13); add(12.2, 13.3); 相同实体(add())有时会 ...

  6. CLR基础,CLR运行过程,使用dos命令创建、编译、运行C#文件,查看IL代码

    CLR是Common Language Runtime的缩写,是.NET程序集或可执行程序运行的一个虚拟环境.CLR用于管理托管代码,但是它本身是由非托管代码编写的,并不是一个包含了托管代码的程序集, ...

  7. 2022Java学习笔记七十三(异常处理:运行时异常、编译时异常、异常的默认处理的流程)

    2022Java学习笔记七十三(异常处理:运行时异常.编译时异常.异常的默认处理的流程) 一.异常体系 1.Exception:java.lang包下,称为异常类,它表示程序本身可以处理的问题 2.R ...

  8. 常见的运行时异常。(Java)

    运行时异常的概念: 继承自RuntimeException的异常或者其子类, 编译阶段是不会出错的,它是在运行时阶段可能出现的错误, 运行时异常编译阶段可以处理也可以不处理,代码编译都能通过!! 主要 ...

  9. 多态和接口(3)——设计模式(1)——方法override、CLR(Common Language Runtime 公共语言运行时)、CTS(Common Type System 公共语言系统)

    1.多态就是设计模式!!!多态理解了设计模式就理解了. 2.定义坐标类,坐标类默认继承Object的ToString()方法,没有自己的特色. 3.override ToString()输出友好信息: ...

最新文章

  1. Styling with the DataGridColumnStyle
  2. 组策略管理——软件限制策略(5)
  3. MyBatisPlus插件扩展_PerformanceInterceptor性能分析插件的使用
  4. laravel大型项目系列教程(四)之显示文章列表和用户修改文章
  5. 贪心算法之高级钟点秘书会议安排问题
  6. 基于JAVA+SpringBoot+Vue+Mybatis+MYSQL的图书馆管理系统
  7. ​php mysql教学管理系统计算机毕业设让网站作品
  8. mysql5.6 主从_mysql5.6 主从配置
  9. HtmlTextWriter类的学习
  10. IntelliJ IDEA插件-翻译插件
  11. 3D目标检测基础知识
  12. MSP430F149串口收发程序详解
  13. 怎么解决idea中yaml无法识别或者飘红?
  14. ACO蚁群算法(附MATLAB源码)
  15. c/c++ read 函数和 write 函数
  16. 使用perl脚本语言处理文本文件
  17. 1.1 显函数的图形
  18. 垃圾分类网站 web前端 + java后端
  19. linux安装elasticsearch-head (es可视化界面)
  20. 宇视摄像机RTSP地址格式规则

热门文章

  1. ng的link和comepile
  2. 基础排序算法 – 冒泡排序Bubble sort
  3. .NET 6 攻略大全(三)
  4. 解读最新的 Xamarin 更新
  5. C# WPF MVVM开发框架Caliburn.Micro快速搭建③
  6. Dapr + .NET 实战(十三)跨语言开发
  7. C#怎么测试静态方法?我给出了2种方案
  8. C# Hashtable和Dictionary区别
  9. 浏览器缓存机制的研究分享
  10. 从工作经历和实践理论看工业互联网的发展