在上一篇文章CLR怎樣實現虛方法的多態調用(1)中主要介紹了CLR怎樣多態調用虛方法以及各種類型 的方法在Method Table中的排布,但是沒有介紹怎樣調用接口方法,當某個對象向上轉型為接口時進行多 態調用時,CLR是怎樣實現的呢?以下面這段代碼為例來說明:
namespace Demo
{
public interface IFoo
{
void Foo();
}
public class Base : IFoo
{
public void Foo()
{
Console.WriteLine("In base's Foo function");
}
}
class Program
{
static void Main(string[] args)
{
IFoo i = new Base();
i.Foo();
}
}
}
在Essential .NET中,Don Box向讀者簡單描述了基於接口的多態調用,在堆中有一個全局接口映射表 ,當某個類實現了一個接口,就會在這個接口表中增加項,而增加的這些項又指向這個具體類的Method Table中的Method,可能說的不是太清楚,就用個圖來表示:
當進行方法調用的時候,首先通過對象找到該類型的Method Table,根據偏移量找到指向Interface Offset Table的指針來定位這個Interface Offset Table,然後CLR查找調用方法在這個Offset Table的 偏移量,最後調用該方法。調用的匯編代碼如下:
mov ecx, esi -- 保存對象地址到ecx中
mov eax, dword ptr [ecx] -- 把類型的Method Table的地址保存在eax中
mov eax, dword ptr [eax+0ch] -- 把Interface Offset Table的地址保存在eax中
mov eax, dword ptr [eax + interface offset] -- 根據Interface在Table中的偏移量,找到其地址 並保存到eax中
call dword ptr [eax + method offset] -- 根據該方法的偏移量定位改方法進行調用
可以說這樣的調用邏輯是很清楚容易讓人理解的。
但是當我用windbg進行跟蹤的時候卻發現接口方法調用機制和上面所說的不同,並沒有一個查找 Interface Offset Table的過程,在Main函數裡是這樣的調用:
mov ecx, esi -- 保存對象地址到ecx中
call dword ptr ds:[980010h] 在數據段980010h上保存的是一個指針,實際上調用的是:
jmp mscorwks!ResolveWorkerAsmStub
可以看到跳轉到ResolveWorkerAsmStub函數裡去了。而這個函數是做什麼的呢,下面的代碼是從SSCLI 裡面找到的(有興趣的可以看看virtualcallstubcpu.hpp):
__declspec (naked) void ResolveWorkerAsmStub()
{
// 首先保存寄存器狀態
call VirtualCallStubManager::ResolveWorkerStatic //調用ResolveWorkerStatic方法
//還原寄存器狀態
jmp eax //eax保存著實際上要調用的方法的地址,所以這裡就開始了方法調用
}
所以猜想到在VirtualCallStubManager::ResolveWorkerStatic函數裡面正確找到了方法的地址,保存 在eax裡。
看來到底是怎樣取到該方法地址這個問題只能等下次有時間再用windbg跟蹤。如果有人了解,也希望 能解釋一下來幫助我解答疑惑。