CLR运行时细节 - 继承多态的实现
2017-03-03 07:10
363 查看
关于多态不多解释了,在运行时决定和调用具体的实现,是面向对象的基础 设计模式的基础.
准备把继承多态和接口多态分开,因为从CLR实现的角度继承多态相比于接口多态要简单得多,也更容易理解,本篇只讨论继承多态, .NET Framework 2.0 和 4.0 这两个版本在实现上稍微有点区别(这里先忽略方法Jit编译的过程,只关注实现的方式).
废话不多,先看代码: C# Polymorphism01.cs
编译代码 先用 .Net Framework 2.0 编译:
运行 Polymorphism01_2.0.exe
启动windbg 附加进程 加载SOS
查找对应的模块:
根据模块查找方法表:
先分别看下 BaseClass BrotherClass DerivedOfBrotherClass 这3个继承关系类的方法表(MethodTable)
![](https://ss.csdn.net/p?http://mmbiz.qpic.cn/mmbiz_png/gak2lhVxV6IrMhUqZnibyC9hn54F8dOuPd0jiadENMptWUY5dqt6ZweRKwLflh6VFopYh1d3wsZw0pG7WWxlqfOA/640?wx_fmt=png)
![](https://ss.csdn.net/p?http://mmbiz.qpic.cn/mmbiz_png/gak2lhVxV6IrMhUqZnibyC9hn54F8dOuPAS9Rc6omwhsEWkfXGGWSmnXUZ2ic1ialCkOENvZLNWfrTQ7xibeSs5SFQ/640?wx_fmt=png)
![](https://ss.csdn.net/p?http://mmbiz.qpic.cn/mmbiz_png/gak2lhVxV6IrMhUqZnibyC9hn54F8dOuP6DvoURFnk5TLKucsjAwWUeU9BLyZxtiaRdia8kZ7SOOibnxlor2ZpicmTA/640?wx_fmt=png)
可以看到第一个虚方法(ToString)的入口都是在方法表偏移28h的位置,其顺序是先父类,再子类,这样的安排让所有同一个家族(继承关系)的类型继承虚方法的顺序是一样的,并且偏移量是一样的,所有的类型(除了接口类型)的父类都是(或者间接是)System.Object,所以前4个虚方法肯定是Object里的4个虚方法(ToString Equals GetHashCode Finalize)
通过Program 的方法表(MethodTable)找到Main方法的入口地址:
Main方法已经Jit编译,看看被编译成啥样子:
这里最重要的几行:
可以看到 继承多态在CLR运行时的实现是通过方法表的偏移 间接调用的,而方法表内继承虚方法的构建顺序是先父类再子类,由于.NET是单一继承,这样就确保了在同一家族的同一虚方法的偏移量是一样的.
接下来用Framework 4.0 编译下源码,4.0 和2.0相比 在实现上多了一层间接寻址,但思路是一样的
运行 Polymorphism01_4.0.exe
启动windbg 附加进程 加载SOS (这里要加载对于4.0的sos.dll)
直接查找Main方法:
看Main方法的区别:
这里只截取最重要的一段,调用构造器和其他的部分都先忽略
.NET 4.0 比2.0 多了一次间接寻址,就是先偏移到虚表的入口,再从这个入口开始偏移到相应的方法,这样的好处(个人觉得)虚表的存储位置可以更灵活 如果方法表(MT)包含多个可变长结构也没问题 只要入口地址保存在偏移28h的位置即可
相关文章:
CLR运行时细节 - Method Descriptor
原文地址:https://espider.github.io/CLR/inheritance-polymorphism/
.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注
![](https://ss.csdn.net/p?http://mmbiz.qpic.cn/mmbiz/gak2lhVxV6IzK1WqqQ4VMXw51RcUR5Q7EPlULUntWYqFPSqYyXXtBctZ6zAdoiaJgXHv1NHClMt34HLtqaEpdVA/640?wx_fmt=jpeg)
赞赏
人赞赏
准备把继承多态和接口多态分开,因为从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"); } } |
1 2 | %windir%\Microsoft.NET\Framework\v2.0.50727\csc.exe /debug /target:exe /out:e:\temp\Polymorphism01_2.0.exe e:\temp\Polymorphism01.cs pause |
启动windbg 附加进程 加载SOS
查找对应的模块:
!Name2EE *!Polymorphism01_2.0.exe
0:004> !Name2EE *!Polymorphism01_2.0.exe Module: 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 00af302c EEClass: 00af12f4 Module: 00af2c5c Name: Program mdToken: 02000002 (E:\temp\Polymorphism01_2.0.exe) BaseSize: 0xc ComponentSize: 0x0 Number of IFaces in IFaceMap: 0 Slots in VTable: 6 -------------------------------------- MethodDesc Table Entry MethodDesc JIT Name 79286aa0 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[]) |
!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 nop 01010142 8b4df4 mov ecx,dword ptr [ebp-0Ch] // 这里是 ChlidClass的对象地址赋给ecx 01010145 8b01 mov eax,dword ptr [ecx] // 同样ChlidClass的方法表地址赋给eax 01010147 ff5038 call dword ptr [eax+38h] // 调用ChlidClass方法表偏移38h的方法,也是VirtualFun1 方法 0101014a 90 nop 0101014b 8b4df0 mov ecx,dword ptr [ebp-10h] // BrotherClass的对象地址赋给ecx 0101014e 8b01 mov eax,dword ptr [ecx] // BrotherClass方法表地址赋给eax 01010150 ff5038 call dword ptr [eax+38h] // 调用BrotherClass方法表偏移38h的方法,也是VirtualFun1 方法 01010153 90 nop 01010154 8b4dec mov ecx,dword ptr [ebp-14h] // DerivedOfBrotherClass的对象地址赋给ecx 01010157 8b01 mov eax,dword ptr [ecx] // DerivedOfBrotherClass方法表地址赋给eax 01010159 ff5038 call dword ptr [eax+38h] // 调用DerivedOfBrotherClass方法表偏移38h的方法,也是VirtualFun1 方法 0101015c 90 nop 0101015d 8b4dec mov ecx,dword ptr [ebp-14h] // 还是DerivedOfBrotherClass对象地址 01010160 8b01 mov eax,dword ptr [ecx] // DerivedOfBrotherClass的方法表赋给eax 01010162 ff503c call dword ptr [eax+3Ch] // 这次偏移不一样了,第6个方法 VirtualFun2 (28h+5×4字节) 01010165 90 nop 01010166 8b4de8 mov ecx,dword ptr [ebp-18h] // 还是DerivedOfBrotherClass对象地址 01010169 8b01 mov eax,dword ptr [ecx] // DerivedOfBrotherClass的方法表赋给eax 0101016b ff5040 call dword ptr [eax+40h] // 这次偏移又不一样了,第7个方法 VirtualFun3 (28h+6×4字节) |
接下来用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.cs pause |
启动windbg 附加进程 加载SOS (这里要加载对于4.0的sos.dll)
直接查找Main方法:
!Name2EE Polymorphism01_4.0.exe Program.Main
0:004> !Name2EE Polymorphism01_4.0.exe Program.Main Module: 00b32ea4 Assembly: Polymorphism01_4.0.exe Token: 06000001 MethodDesc: 00b33838 Name: Program.Main(System.String[]) JITTED Code Address: 033a0070 |
!u 033a0070
这里只截取最重要的一段,调用构造器和其他的部分都先忽略
... e:\temp\Polymorphism01.cs @ 16: 033a0139 8b4df8 mov ecx,dword ptr [ebp-8] // 这个还是一样BaseClass对象的地址赋给ecx 033a013c 8b01 mov eax,dword ptr [ecx] // 还是对象的第一个4字节是方法表地址 赋给eax 033a013e 8b4028 mov eax,dword ptr [eax+28h] // 这里是和2.0的区别 所有继承的虚方法的起始地址保存在方法表偏移28h的位置,也就是偏移量不是从方法表地址开始算了 033a0141 ff5010 call dword ptr [eax+10h] // 这里的方式一样的 eax是虚方法的起始位置了,前4个是Object的4个虚方法,偏移10h是第5个方法 VirtualFun1 033a0144 90 nop e:\temp\Polymorphism01.cs @ 17: 033a0145 8b4df4 mov ecx,dword ptr [ebp-0Ch] // ChlidClass对象地址赋给ecx 033a0148 8b01 mov eax,dword ptr [ecx] // ChlidClass方法表地址赋给eax 033a014a 8b4028 mov eax,dword ptr [eax+28h] // 虚表入口地址赋给eax 033a014d ff5010 call dword ptr [eax+10h] //还是偏移到第5个方法 VirtualFun1 033a0150 90 nop e:\temp\Polymorphism01.cs @ 18: 033a0151 8b4df0 mov ecx,dword ptr [ebp-10h] // BrotherClass对象地址赋给ecx 033a0154 8b01 mov eax,dword ptr [ecx] // BrotherClass方法表地址赋给eax 033a0156 8b4028 mov eax,dword ptr [eax+28h] // 虚表入口地址赋给eax 033a0159 ff5010 call dword ptr [eax+10h] //还是偏移到第5个方法 VirtualFun1 033a015c 90 nop e:\temp\Polymorphism01.cs @ 19: 033a015d 8b4dec mov ecx,dword ptr [ebp-14h] // DerivedOfBrotherClass对象地址赋给ecx 033a0160 8b01 mov eax,dword ptr [ecx] // DerivedOfBrotherClass方法表地址赋给eax 033a0162 8b4028 mov eax,dword ptr [eax+28h] // 虚表入口地址赋给eax 033a0165 ff5010 call dword ptr [eax+10h] //还是偏移到第5个方法 VirtualFun1 033a0168 90 nop e:\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个方法 VirtualFun2 033a0174 90 nop e:\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个方法 VirtualFun3 033a0180 90 nop ... |
参考文档:
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跨平台或扫描二维码关注
赞赏
人赞赏
相关文章推荐
- JAVA中如何用接口实现多继承和多态 (非常好)
- CLR怎样实现虚方法的多态调用(2)
- WCF学习笔记(三)使用(Service)KnownType标记实现继承、多态特性
- C语言实现封装、继承和多态
- 关于C语言中继承和多态的实现
- 继承和多态在C#中的实现
- C++虚函数实现动态多态运行
- sogou interview ==> C 实现 C++ 封装 继承 多态
- 关于C语言中继承和多态的实现
- Java中使用接口实现多继承和多态的方法
- 多重继承多态的实现
- 关于C语言中继承和多态的实现
- 在lua里实现常量_类_继承_多态
- 关于ActionScript 3.0 中方法重写和继承的发现(运行时多态,原理同Java)
- CLR怎样实现虚方法的多态调用(1)
- javascript OOP:实现继承、多态与封装
- javascript 中实现继承及多态的例子
- 【转】C语言实现C++面向对象的封装、继承、多态机制
- Vb中 继承 多态的实现
- 实现C#继承与C#多态的实例演示