2014-10-30 15 views
5

Po usunięciu Ldstr "a" i Call Console.WriteLine (przed Ret), kod działa poprawnie, w przeciwnym razie po wywołaniu zostanie zgłoszony kod InvalidProgramException. Czy to oznacza, że ​​wymagany jest pusty stos ewaluacyjny?Czy przed blokiem wyjątków wymagany jest pusty stos oceny?

class Program 
{ 
    delegate void Del(); 

    static void Main(string[] args) 
    { 
     DynamicMethod dynamicMethod = new DynamicMethod("", null, Type.EmptyTypes); 
     ILGenerator ilGen = dynamicMethod.GetILGenerator(); 
     ilGen.Emit(OpCodes.Ldstr, "a"); 

     ilGen.BeginExceptionBlock(); 
     ilGen.Emit(OpCodes.Ldstr, "b"); 
     ilGen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", BindingFlags.Static | BindingFlags.Public, null, new Type[] { typeof(string) }, null)); 
     ilGen.BeginCatchBlock(typeof(Exception)); 
     ilGen.EndExceptionBlock(); 

     ilGen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", BindingFlags.Static | BindingFlags.Public, null, new Type[] { typeof(string) }, null)); 
     ilGen.Emit(OpCodes.Ret); 

     ((Del)dynamicMethod.CreateDelegate(typeof(Del))).Invoke(); 
    } 
} 
+0

Podejrzewam, że tak jest (że stos oceny musi być pusty przed wprowadzeniem nowej ramki wyjątków), ale nie mogę teraz znaleźć oficjalnego odniesienia. –

Odpowiedz

7

Aby zrozumieć to, co robisz, proponujemy, aby jak minimalny przykład, jak to możliwe.

ilGen.Emit(OpCodes.Ldstr, "a"); 

ilGen.BeginExceptionBlock(); 
ilGen.BeginCatchBlock(typeof(Exception)); 
ilGen.EndExceptionBlock(); 

ilGen.Emit(OpCodes.Pop); 
ilGen.Emit(OpCodes.Ret); 

Po tym, można użyć AssemblyBuilder zrzucić dany kod do pliku wykonywalnego. Jeśli to zrobisz, ildasm pokaże, co zostało wygenerowane.

// Code size  17 (0x11) 
    .maxstack 2 
    IL_0000: ldstr  "a" 
    .try 
    { 
    IL_0005: leave  IL_000f 
    } // end .try 
    catch [mscorlib]System.Exception 
    { 
    IL_000a: leave  IL_000f 
    } // end handler 
    IL_000f: pop 
    IL_0010: ret 

Jak widać, będziemy sięgać do instrukcji leave który skacze do pop. Można wtedy google o leave, który stanowi, że:

Dyspozycja urlopu jest podobna do instrukcji BR, ale może to być wykorzystywane do wyjść spróbować, filtra lub bloku catch, natomiast zwykłym oddziale instrukcji może używaj tylko w takim bloku do przeniesienia w nim kontroli. Instrukcja opuszczania opróżnia stos oceny i zapewnia, że ​​zostaną wykonane odpowiednie końcowe bloki końcowe.

Dlaczego jednak następujące czynności nie działają?

ilGen.Emit(OpCodes.Ldstr, "a"); 

ilGen.BeginExceptionBlock(); 
ilGen.BeginCatchBlock(typeof(Exception)); 
ilGen.EndExceptionBlock(); 

//ilGen.Emit(OpCodes.Pop); 
ilGen.Emit(OpCodes.Ret); 

Podejrzewam, że nie jest to "limit fizyczny", ale problem z weryfikacją. Uciekajmy peverify ourapp.exe i zobacz, co mamy:

[IL]: Error: [C:\temp\test.exe : Program::Main][offset 0x00000005] Attempt to en 
ter a try block with nonempty stack. 
1 Error(s) Verifying C:\temp\test.exe 

W tym momencie może być jak Wat? Przy odrobinie googlowania możesz wymyślić kod błędu: 0x801318A9. Szybkie skanowanie przez źródeł SSCLI2.0:

case ReaderBaseNS::RGN_TRY: 
    // Entering a try region, the evaluation stack is required to be empty. 
    if (!m_readerStack->empty()) { 
     BADCODE(MVER_E_TRY_N_EMPTY_STACK);      
    } 
    break; 

Teraz, to jest fajne, ale jeśli jesteś naukowy, można się zastanawiać, dlaczego stos ocena musi być pusta?

W tym celu prawdopodobnie warto spojrzeć na ECMA C# and Common Language Infrastructure Standards. Podejrzeń, że możesz znaleźć powód z PartitionIII CIL.pdf

+0

'peverify', niesamowite! – Chris

Powiązane problemy