通过指向基类的指针实现虚拟方法调用的CLR

时间:2013-11-17 16:46:21

标签: c# .net clr vtable

在我知道如何通过C#/ CLR回忆所需的虚拟方法之前,我无法入睡。在我的书中,richter写的不多于CLR确定对象的实际类型并调用适当的方法。例如,在C ++中,多态类的每个实例都存储一个指向虚拟表的指针。但是在C#而不是指向虚拟表的指针中,实例数据存储了一些SyncBlk Index和TypeHandle。不明白TypeHandle与C#中的这个指针有什么不同。 TypeHandle的作用是什么。 例如,在C ++中我们有

class A
{
   int a;
   public virtual void show() {}
};

class B: public A
{
   int b;
   public virtual void show() {}
};

A和B类的实例如何在内存中查找,我用伪代码编写

A:
{
  vtptr;   // pointer to  A vt
  a;
}


B: 
{
  vtptr;  // pointer to  A vt + B vt
  a;
  b;
}

然后在C ++中我们运行代码

A* pa = new B(); 
pa->show();  

很明显,我们创建B实例并将其转换为A类型,但我们不会丢失show()的重写地址,并且感谢我们可以调用B :: show()。    我真的需要了解C#/ CLR如何执行强制转换为基类型并定义虚方法调用的类似示例。请帮忙!我很高兴知道所有技术细节

1 个答案:

答案 0 :(得分:2)

“TypeHandle”是一个流行的用词不当。它实际上是MethodTable指针,它在CLR源中的命名方式。托管对象的MethodTable在功能上非常类似于C ++ v表,一个指向类方法的指针表。一个小的区别是它不仅包含虚方法,该表还用于及时编译方法。

所以它只是以完全相同的方式工作,对该表中的条目进行简单的间接调用。抖动知道表中方法指针的偏移量,就像C ++编译器一样。它的运行速度与本机C ++代码一样快。

您使用C#编写的代码段并使用如下:

    A obj = new B();
    obj.show();

在运行时生成此32位机器代码:

00000003  mov         ecx,51B034Ch            ; typeref for class B           
00000008  call        FBB90BF4                ; call operator new

0000000d  mov         ecx,eax                 ; setup this pointer
0000000f  mov         eax,dword ptr [ecx]     ; obtain methodtable pointer from object
00000011  call        dword ptr [eax+38h]     ; indirect call to show()

从偏移量(0x38)可以看出,方法表不仅包含方法指针。您可以在SSCLI20源代码中找到有关它的详细信息。

原生C ++中的代码片段并使用如下:

int main()
{
    A* obj = new B;
    obj->show();
    return 0;
}

生成此32位机器代码:

01361010  push        0Ch                     ; size of B object 
01361012  call        operator new (136104Ch) ; call operator new
01361017  add         esp,4                   ; __cdecl stack cleanup
0136101A  test        eax,eax                 ; handle null pointer
0136101C  je          main+1Fh (136102Fh) 
0136101E  mov         dword ptr [eax],offset B::`vftable' (1362100h)  ; initialize v-table 

01361024  mov         edx,dword ptr [eax]     ; obtain v-table pointer from object
01361026  mov         ecx,eax                 ; setup this pointer
01361028  mov         eax,dword ptr [edx]     ; get method pointer
0136102A  call        eax                     ; indirect call to show()

由于C ++语言规则,构造函数调用稍微复杂一些。方法调用是相同的,只是不同的代码。在早期的奔腾处理器上效率稍高。