2016-04-08 10 views
7

Bedenken Sie: Wie handhabt C# den Aufruf einer Schnittstellenmethode in einer Struktur?

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

Und die IL für Main:

.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 

First (IL_000a), S::M() mit einem Werttyp für this genannt wird. Next (IL_0011), wird es mit einem Referenz (Boxed) -Typ aufgerufen.

Wie funktioniert das?

Ich denke an drei Möglichkeiten können:

  1. Zwei Versionen von I::M kompiliert werden, für Wert/ref Typ. In der vtable speichert es die eine für ref-Typ, aber statisch abgesetzte Aufrufe verwenden die eine für Werttypen. Das ist hässlich und unwahrscheinlich, aber möglich.
  2. In der Vtable speichert es eine "Wrapper" -Methode, die this ausbindet, ruft dann die eigentliche Methode auf. Das klingt ineffizient, weil alle Argumente der Methode durch zwei Aufrufe kopiert werden müssten.
  3. Es gibt spezielle Logik, die dies in callvirt überprüft. Noch ineffizienter: Alle callvirt s verursachen einen (leichten) Nachteil.
+0

Hinweis 'IL_0002: Box S'. Die Struktur ist eingerahmt, um den Aufruf zu ermöglichen. Zwar gibt es einen Weg mit Generika. – Joey

+0

@Joey das ist für 'I i = s;'; der erste Aufruf verwendet den Werttyp 'ldloca.s s' – valtron

+1

Es ist verschachtelt und das Betrachten der IL hilft überhaupt nicht. Ein grundlegendes Verständnis der Verwendung von Dispatch-Stubs in der CLR ist erforderlich. Vance Morrison vom CLR-Team erklärt es ziemlich gut in [diesem Blogpost] (https://blogs.msdn.microsoft.com).com/vancem/2006/03/13/digeing-in-interface-Aufrufe-im-Netzwerk-Framework-stub-based-dispatch /). –

Antwort

2

Die kurze Antwort ist, dass die in dem Verfahren selbst, wird der Wert der struct immer über einen Zeiger zugegriffen wird. Das bedeutet, dass die Methode nicht so funktioniert, als ob der struct als normaler Parameter übergeben wurde, es ist eher wie ein ref Parameter. Es bedeutet auch, dass die Methode nicht weiß, ob sie mit einem Boxed-Wert arbeitet oder nicht.

Die lange Antwort:

Erstens, wenn ich Ihren Code kompilieren, dann ist s.M(); keinen Code generieren. Der JIT-Compiler ist intelligent genug, um die Methode zu inline zu schreiben, und eine leere Methode führt zu keinem Code. Also, was ich getan habe, ist [MethodImpl(MethodImplOptions.NoInlining)] auf S.M zu wenden, um dies zu vermeiden.

Nun, hier ist der native Code Ihrer Methode (Weglassen Funktion prolog und Epilog) erzeugt:

// 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] 

In beiden Fällen ist die call endet mit dem gleichen Code aufrufen (die nur eine einzige ret Anweisung) . Das erste Mal zeigt das CX-Register auf den stack-allokierten s (SP + 28h im obigen Code), das zweite Mal auf den Heap-allokierten i (AX + 8 direkt nach dem Aufruf der Heap-Zuweisungsfunktion).

+0

Also 'this' ist immer ein Zeiger; In einer ref-Typ-Methode zeigt es auf das Typ-Informationsfeld (unmittelbar vor dem ersten Feld) und in einem Wert-Typ zeigt es direkt auf das erste Feld, das ein interner Zeiger auf einen Box-Wert sein kann. – valtron