2012-03-21 57 views
29

出於好奇:CLR如何調用虛擬方法調用接口成員到正確的實現?CLR實現對接口成員的虛擬方法調用

我知道CLR爲每種類型的方法插槽維護的VTable,事實上每個接口都有一個指向相關接口方法實現的方法插槽附加列表。但我不明白以下內容:CLR如何有效地確定從類型的VTable中選擇哪個接口方法槽列表?

來自MSDN雜誌2005年5月號的文章Drill Into .NET Framework Internals to See How the CLR Creates Runtime Objects討論了由接口ID索引的進程級映射表IVMap。這是否意味着同一進程中的所有類型都具有指向同一個IVMap的相同指針?

它還指出:

如果MyInterface1由兩個類來實現,也將在IVMap表中的兩個 條目。該條目將指向MyClass方法表中嵌入的子表的開始 。

CLR如何知道選擇哪個條目?它是否進行線性搜索來查找與當前類型匹配的條目?或二進制搜索?或者某種直接索引,並有一個可能有很多空條目的地圖?

我也通過C#第3版閱讀了CLR中的接口一章,但它沒有談論這個。因此,this other question的答案不回答我的問題。

+0

問題中的鏈接不再指向特定文章。請你能告訴我們該文章出現在(月份和年份)的哪個問題? – buffjape 2015-12-14 14:57:48

+1

@buffjape這是[MSDN magazine](https://msdn.microsoft.com/en-us/magazine/ee310108.aspx)2005年5月刊。我更新了鏈接以指向互聯網存檔。 – Virtlink 2015-12-15 10:05:49

+0

請不要獎勵一個巨大的賞金,即使在發佈的時候,也不相關的嚴重過時的複製粘貼答案。這對任何人都沒有幫助。 – 2016-10-30 12:09:29

回答

21

.NET Stack

如果你看看圖,這是鏈接的網站上,它可能更容易理解。

這是否意味着同一個進程中的所有類型都有相同的指向同一個IVMap的指針?

是的,因爲它在域級別,這意味着AppDomain中的所有內容都具有相同的IVMap。

CLR如何知道選擇哪個條目?它是否進行線性搜索來查找與當前類型匹配的條目?或二進制搜索?或者某種直接索引,並有一個可能有很多空條目的地圖?

這些類都是用偏移量來佈局的,所以每個類都有一個相對設置的區域。這使得查找方法更容易。它將搜索IVMap表並從界面中找到該方法。從那裏,它進入MethodSlotTable並使用該類的接口實現。類的接口映射包含元數據,但是,實現與其他方法一樣被處理。

再次從該網站您鏈接:

每個接口的實現將在IVMap的條目。如果MyInterface1由兩個類實現,則IVMap表中將有兩個條目。該項目將重新指向嵌入MyClass的方法表中的子表的開始

這意味着,每一個接口實現的時候它在IVMap唯一的記錄,它指向MethodSlotTable這又指向實施。所以它知道基於調用它的類選擇哪個實現,因爲IVMap記錄指向調用該方法的類中的MethodSlotTable。所以我想這只是通過IVMap進行線性搜索才能找到正確的實例,然後關閉並運行。


編輯:提供更多關於IVMap的信息。

再次,從在OP的鏈接:

前4個字節的第一InterfaceInfo入口點MyInterface1的類型句柄(參見圖9和圖10)的。下一個WORD(2個字節)被Flags佔用(其中0從父類繼承,1在當前類中實現)。 Flags之後的WORD是Start Slot,由類加載器用來佈置接口實現子表。

所以在這裏我們有一個表,其中數字是字節的偏移量。這是IVMap只是一條記錄:

+----------------------------------+ 
| 0 - InterfaceInfo    | 
+----------------------------------+ 
| 4 - Parent      | 
+----------------------------------+ 
| 5 - Current Class    | 
+----------------------------------+ 
| 6 - Start Slot (2 Bytes)   | 
+----------------------------------+ 

假設有這個AppDomain中100分接口的記錄,我們需要找到每一個實施。我們只比較第5個字節,看它是否與我們當前的類匹配,如果是,我們跳到第6個字節中的代碼。因爲,每一個記錄是8個字節長,我們需要做的是這樣的:(僞代碼)

findclass : 
    if (!position == class) 
     findclass adjust offset by 8 and try again 

雖然它仍然是一個線性搜索,在現實中,它是不會採取這種只要正在迭代的數據大小並不是很大。我希望有所幫助。


EDIT2:

所以看圖表,不知道爲什麼會出現在IVMap爲圖中的類沒有插槽1我重新閱讀部分,發現這個後:

IVMap基於嵌入在方法表中的接口映射信息創建。接口映射是在MethodTable佈局過程中基於類的元數據創建的。一旦類型加載完成,只有IVMap用於方法調度。

因此,類的IVMap只加載了特定類繼承的接口。它看起來像從域IVMap複製,但只保留指向的接口。這引出了另一個問題,如何?可能的是這是C++如何做的vtables其中每個條目具有偏移和接口映射提供了偏移的列表中IVMap以包括等效。

如果我們看一下IVMap這可能是這整個域:

+-------------------------+ 
| Slot 1 - YourInterface | 
+-------------------------+ 
| Slot 2 - MyInterface | 
+-------------------------+ 
| Slot 3 - MyInterface2 | 
+-------------------------+ 
| Slot 4 - YourInterface2 | 
+-------------------------+ 

假定只有4個接口映射的實現在這一領域。每個插槽將有一個偏移量(類似於IVMap記錄我先前公佈),併爲該類會使用這些偏移訪問記錄在IVMap的IVMap。

假設每個時隙爲8個字節插槽1從0開始,所以如果我們想獲得插槽2和3,我們會做這樣的事:因爲我不是

mov ecx,edi 
mov eax, dword ptr [ecx] 
mov eax, dword ptr [ecx+08h] ; slot 2 
; do stuff with slot 2 
mov eax, dword ptr [ecx+10h] ; slot 3 
; do stuff with slot 3 

請原諒我的x86熟悉它,但我試圖複製它們鏈接到的文章中的內容。從那時起

+0

我不會跟隨偏移量的偏移量,所以每個東西都有一個相對設置的區域,它將會在哪裏。但是,是的,我可以想象,CLR通過IVMap進行線性搜索,正如我的帖子所述。但我想知道它在實踐中如何運作。當然,對於像經常發生的界面方法調用這樣的事情來說,做一個線性搜索非常幼稚。 – Virtlink 2012-03-21 19:54:57

+0

@Virtlink如果你看看上面的圖片,你會注意到有一個基於方法表的堆的數字。所以GCInfo在-12,基本實例大小是在4等等。使用這些設置,標準化的偏移量,你可以通過像'dword ptr [eax + 0Ch]'' – Jetti 2012-03-21 19:59:22

+0

那樣找到字段。好吧,是的,你是正確的。但我從來沒有在我的問題的任何地方提到過_fields_這個詞:)。我想知道IVMap是什麼樣子的,以及CLR如何使用它從已知的接口+方法槽索引以及對象類型的VTable中包含接口方法槽的子表的對象引用。 – Virtlink 2012-03-21 20:04:09

0

從你鏈接的第一篇文章:

如果MyInterface1由兩個類來實現,也將在IVMap表中的兩個 條目。該條目將指向回到開始嵌入MyClass的方法表中的子表的 ,如圖 圖9

的類加載器遍歷當前類的元數據, 父類和接口,並創建方法表。在 佈局過程中,它將替換任何重寫的虛擬方法,替換 隱藏的任何父類方法,創建新的插槽,並根據需要複製插槽。插槽的重複是必要的 創建一個錯覺,每個接口都有自己的小虛擬表。 但是,重複的插槽指向相同的物理實現。

這表明,我認爲該接口的IVMap具有指向類的虛函數表,基本上都有各自的實現該接口的類的方法重複實現的款類名(或同等學歷)鍵條目,並指向與課程自己的vtable條目相同的物理實現。

雖然可能是完全錯誤的。

+0

如果您完全閱讀我的文章,您會看到我已經引用了您所做的同樣的引語,並且我知道VTable如何爲每個接口指定方法實現的子節。我的問題具體是CLR如何知道使用哪個小節。 – Virtlink 2012-03-21 19:47:17

13

這篇文章是超過10歲,很多已經改變。

IVMaps現已被Virtual Stub Dispatch代替

虛擬存根調度(VSD)是將存根用於虛擬方法調用而不是傳統虛擬方法表的技術。過去,接口調度要求接口具有過程唯一標識符,並且每個加載的接口都添加到全局接口虛擬表映射中。

去閱讀那篇文章,它有更多的細節你永遠需要知道。它來自Book of the Runtime,這是文件最初寫的CLR開發者對開發者CLR,但現在已經被公佈給大家。它基本上描述了運行時的膽量。

沒有意義,對我來說,這裏複製的文章,但我只是陳述要點和它們意味着什麼:

  • 當JIT看到調用接口成員,它編譯成一個查找存根。這是一段代碼將調用通用解析器
  • 通用解析器是一個函數,它將找出調用哪個方法。這是調用這種方法的最通用和最慢的方式。當從查找存根第一次呼叫時,它將修補該存根(在運行時重寫其代碼)到調度存根。它還會生成一個解決方案存根供以後使用。 查找存根在這一點上消失。
  • 一個調度存根是調用接口成員的最快方法,但有一個問題:它是關於調用是單態,這意味着它的情況下優化時,接口調用始終解析樂觀相同的具體類型。它將對象的方法表(即具體類型)與先前看到的(硬編碼到存根)進行比較,如果比較成功,則調用緩存的方法(其地址也是hardocded)。如果失敗,則回退到解決存根
  • 解決存根處理多態調用(一般情況下)。它使用緩存來查找要調用的方法。如果該方法不在緩存中,則調用通用解析器(也寫入該緩存)。

這裏是一個重要的考慮因素,直接從文章:

當調度存根無法足夠頻繁,調用點被認爲是多態和決心存根將支持修補通話網站直接指向解決方案存根以避免始終發生失敗的調度存根的開銷。在同步點(目前是GC的結尾),假設呼叫站點的多態屬性通常是暫時的,則多態站點將被隨機地提升回單形呼叫站點。如果這個假設對於任何特定的呼叫站點都是不正確的,那麼它會很快觸發一次派遣將其降級爲多態。

運行時是真的看好約單態調用點,這讓很多的意義上真正的代碼,它會盡量避免決心存根儘可能。

+0

@布魯諾謝謝! :) – 2016-11-03 08:45:00

相關問題