2016-04-08 17 views
7

पर विचार करें: सी # हैंडल एक स्ट्रक्चर पर इंटरफ़ेस विधि को कैसे कॉल करता है?

interface I { void M(); } 
struct S: I { public void M() {} } 
// in Main: 
S s; 
I i = s; 
s.M(); 
i.M(); 

और मुख्य के लिए आईएल:

.maxstack 1 
.entrypoint 
.locals init (
    [0] valuetype S s, 
    [1] class I i 
) 

IL_0000: nop 
IL_0001: ldloc.0 
IL_0002: box S 
IL_0007: stloc.1 
IL_0008: ldloca.s s 
IL_000a: call instance void S::M() 
IL_000f: nop 
IL_0010: ldloc.1 
IL_0011: callvirt instance void I::M() 
IL_0016: nop 
IL_0017: ret 

पहले (IL_000a), S::M()this के लिए एक मान प्रकार के साथ कहा जाता है। अगला (IL_0011), इसे संदर्भ (बॉक्स किए गए) प्रकार के साथ बुलाया जाता है।

यह कैसे काम करता है?

मैं तीन तरीके के बारे में सोच सकते हैं:

I::M की
  1. दो संस्करण मूल्य/रेफरी प्रकार के लिए संकलित कर रहे हैं। Vtable में, यह एक को रेफ प्रकार के लिए संग्रहीत करता है, लेकिन स्थैतिक रूप से प्रेषित कॉल मूल्य प्रकार के लिए उपयोग करते हैं। यह बदसूरत और असंभव है, लेकिन संभव है।
  2. vtable में, यह एक "रैपर" विधि संग्रहीत करता है जो this को अनबॉक्स करता है, फिर वास्तविक विधि को कॉल करता है। यह अक्षम लगता है क्योंकि सभी विधि के तर्कों को दो कॉल के माध्यम से कॉपी करना होगा।
  3. विशेष तर्क है जो callvirt में इसकी जांच करता है। और भी अक्षम: सभी callvirt एस (मामूली) जुर्माना लगाते हैं।
+0

नोट 'IL_0002: बॉक्स एस'। कॉल को अनुमति देने के लिए संरचना को बॉक्स किया गया है। यद्यपि जेनेरिक के साथ इसके आसपास एक रास्ता है। – Joey

+0

@ जॉय जो 'i i = s; 'के लिए है; पहला कॉल मान प्रकार 'ldloca.s s' – valtron

+1

का उपयोग करता है यह गठित है और आईएल को देखकर बिल्कुल मदद नहीं मिलती है। सीएलआर में प्रेषण स्टब्स के तरीके की मूल समझ का उपयोग आवश्यक है। सीएलआर टीम के वेंस मॉरिसन ने [इस ब्लॉग पोस्ट] में काफी अच्छी तरह से बताया है (https://blogs.msdn.microsoft।com/vancem/2006/03/13/खुदाई-में-इंटरफ़ेस-कॉल-इन-निवल ढांचा-ठूंठ आधारित-प्रेषण /)। –

उत्तर

2

संक्षिप्त उत्तर है कि विधि अपने आप में, struct का मूल्य हमेशा एक सूचक के माध्यम से पहुँचा रहा है। इसका मतलब है कि विधि संचालित नहीं होती है जैसे struct सामान्य पैरामीटर के रूप में पारित किया गया था, यह ref पैरामीटर की तरह है। इसका यह भी अर्थ है कि विधि यह नहीं जानती कि यह बॉक्स किए गए मान पर काम कर रहा है या नहीं।

विस्तृत उत्तर है:

सबसे पहले, अगर मैं अपने कोड संकलन है, तो s.M(); किसी भी कोड उत्पन्न नहीं करता है। जेआईटी कंपाइलर विधि को रेखांकित करने और किसी भी कोड में खाली विधि परिणामों को रेखांकित करने के लिए पर्याप्त स्मार्ट है। तो, मैंने इससे बचने के लिए S.M पर [MethodImpl(MethodImplOptions.NoInlining)] लागू करना है। ,

// initialize s in register AX 
xor   eax,eax 
// move s from register AX to stack (SP+28h) 
mov   qword ptr [rsp+28h],rax 
// load pointer to MethodTable for S to register CX 
mov   rcx,7FFDB00C5B08h 
// allocate memory for i on heap 
call  JIT_TrialAllocSFastMP_InlineGetThread (07FFE0F824C10h) 
// copy contents of s from stack to register C 
movsx  rcx,byte ptr [rsp+28h] 
// copy from register CX to heap 
mov   byte ptr [rax+8],cl 
// copy pointer to i from register AX to register SI 
mov   rsi,rax 
// load address to c on stack to register CX 
lea   rcx,[rsp+28h] 
// call S::M 
call  00007FFDB01D00C8 
// copy pointer to i from register SI to register CX 
mov   rcx,rsi 
// move address of stub for I::M to register 11 
mov   r11,7FFDB00D0020h 
// ??? 
cmp   dword ptr [rcx],ecx 
// call stub for I::M 
call  qword ptr [r11] 

दोनों ही मामलों में call एक ही कोड (जो सिर्फ एक ret अनुदेश है) पर कॉल समाप्त होता है:

अब, यहाँ मूल कोड अपने विधि उत्पन्न करता है (छोड़ते हुए समारोह prolog और उपसंहार) है । पहली बार, सीएक्स रजिस्टर स्टैक-आवंटित s (उपर्युक्त कोड में एसपी + 28 एच) को इंगित करता है, दूसरी बार ढेर-आवंटित i (हेक्स आवंटन समारोह में कॉल के बाद एएक्स +8)।

+0

तो 'यह' हमेशा एक सूचक है; एक रेफ प्रकार विधि में, यह प्रकार के सूचना फ़ील्ड को इंगित करता है (पहले फ़ील्ड से पहले), और एक मान प्रकार में यह सीधे पहले फ़ील्ड को इंगित करता है, जो कि बॉक्स किए गए मान_ में एक आंतरिक सूचक हो सकता है। – valtron

संबंधित मुद्दे