C# 中的多态底层虚方法调用详情

 更新时间:2022年06月01日 11:53:28   作者:一线码农聊技术  
这篇文章主要介绍了C# 中的多态底层虚方法调用详情,文章围绕主题展开详细的内容介绍,需要的小伙伴你可以参考一下

前言:

本质上来说,CoreCLR 也是 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() 处下一个断点,观察一下它的反汇编代码:

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");
        }
    }
}

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

(1)eax,dword ptr [ebp-8]

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

0:000> dp ebp-8 L1
0057f300  027ea88c
0: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

(2)eax,dword ptr [eax]

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

0:000> dp 027ea88c L1
027ea88c  05ce5d3c

0: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

(3)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+28h 是 m_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 Table
   Entry 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() 方法。

(4)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 Table
   Entry 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 27

D:\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              nop

D:\net6\ConsoleApplication2\ConsoleApp1\Program.cs @ 29:
05cf2286 8b0d74207e04    mov     ecx,dword ptr ds:[47E2074h] ("chinese")
05cf228c e8dffbffff      call    05cf1e70
05cf2291 90              nop

D:\net6\ConsoleApplication2\ConsoleApp1\Program.cs @ 30:
05cf2292 90              nop
05cf2293 8be5            mov     esp,ebp
05cf2295 5d              pop     ebp
05cf2296 c3              ret

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

三、总结

到此这篇关于C# 中的多态底层虚方法调用详情的文章就介绍到这了,更多相关 C# 多态内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解C#如何自定义书写中间件

    详解C#如何自定义书写中间件

    中间件是一种装配到应用管道以处理请求和响应的软件,是介于request与response处理过程之间的一个插件,本文主要介绍了如何自定义书写中间件,需要的可以参考下
    2023-08-08
  • c# 使用HtmlAgilityPack解析Html

    c# 使用HtmlAgilityPack解析Html

    这篇文章主要介绍了c# 如何使用HtmlAgilityPack解析Html,帮助大家利用c#进行爬虫,感兴趣的朋友可以了解下
    2020-12-12
  • VisualStudio2019安装C#环境的实现方法

    VisualStudio2019安装C#环境的实现方法

    这篇文章主要介绍了VisualStudio2019安装C#环境的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • Unity学习之FSM有限状态机

    Unity学习之FSM有限状态机

    这篇文章主要介绍了Unity学习之FSM有限状态机,通过详细的代码案例来进行解析说明,希望这篇文章对你有所帮助
    2021-06-06
  • 详解C#中多态性学习/虚方法/抽象方法和接口的用法

    详解C#中多态性学习/虚方法/抽象方法和接口的用法

    这篇文章主要为大家详细介绍了C#中多态性学习、虚方法、抽象方法和接口的用法的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下
    2023-03-03
  • C#使用InstallerProjects打包桌面应用程序的完整步骤

    C#使用InstallerProjects打包桌面应用程序的完整步骤

    这篇文章主要给大家介绍了关于C#使用InstallerProjects打包桌面应用程序的完整步骤,文中通过示例代码介绍的非常详细,对大家学习或者使用C#具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-07-07
  • C# NetRemoting实现双向通信

    C# NetRemoting实现双向通信

    本篇文章主要介绍了C# NetRemoting实现双向通信,.Net Remoting 是由客户端通过Remoting,访问通道以获得服务端对象,再通过代理解析为客户端对象来实现通信的
    2017-03-03
  • C#调用exe文件的方法详解

    C#调用exe文件的方法详解

    这篇文章主要为大家详细介绍了C#调用exe文件的相关方法,文中的示例代码讲解详细,具有一定的借鉴价值,有需要的小伙伴可以参考一下
    2024-04-04
  • C#中的SQLCommand命令与DbTransaction事务处理

    C#中的SQLCommand命令与DbTransaction事务处理

    这篇文章介绍了C#中的SQLCommand命令与DbTransaction事务处理,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-05-05
  • C# Chart绘制简单图形波形

    C# Chart绘制简单图形波形

    这篇文章主要为大家详细介绍了C# Chart绘制简单图形波形,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02

最新评论