2012-03-09 47 views
10

Po pierwsze, niektóre informacje tła:Reflection.Emit.ILGenerator Obsługa wyjątków „Leave” Instrukcja

Robię kompilatora dla szkolnego projektu. To już działa, a ja wkładam wiele wysiłku, aby naprawić i/lub zoptymalizować go. I niedawno napotkasz problem z jest to, że odkryłem, że obiekt ILGenerator generuje dodatkowy leave dyspozycję podczas wywołania jednej z następujących metod członkowskich:

BeginCatchBlock() 
BeginExceptFilterBlock() 
BeginFaultBlock() 
BeginFinallyBlock() 
EndExceptionBlock() 

Więc rozpocząć oświadczenie spróbować z wywołaniem BeginExceptionBlock(), dodaj kilka klauzul catch z BeginCatchBlock(), ewentualnie dodaj klauzulę finally z BeginFinallyBlock(), a następnie zakończ region chronionego kodu wartością EndExceptionBlock().

Wymienione przeze mnie metody automatycznie generują rozkaz instrukcji leave do pierwszej instrukcji po instrukcji try. Nie chcę tych z dwóch powodów. Jeden, ponieważ zawsze generuje niezoptymalizowaną instrukcję, a nie instrukcję leave.s, nawet jeśli rozgałęzia się tylko o dwa bajty. I dwa, ponieważ nie możesz kontrolować, gdzie idzie instrukcja urlopowa.

Tak więc, jeśli chcesz rozgałęzić się do innej lokalizacji w kodzie, musisz dodać zmienną lokalną generowaną przez kompilator, ustawić ją w zależności od tego, gdzie chcesz wejść do instrukcji try, pozwolić EndExceptionBlock() automatycznie wygenerować instrukcja leave, a następnie wygeneruj instrukcję przełączania poniżej bloku try. OR, można po prostu emitują leave lub leave.s instrukcji samodzielnie, przed skontaktowaniem się z jedną z powyższych metod, powodując brzydkie i nieosiągalny dodatkowych 5 bajtach, tak jak poniżej:

L_00ca: leave.s L_00e5 
L_00cc: leave L_00d1 

Obie te opcje są nie do przyjęcia dla mnie. Czy jest jakiś sposób, aby zapobiec automatycznemu generowaniu instrukcji leave, czy też w inny sposób określić chronione regiony zamiast używać tych metod (które są wyjątkowo irytujące i praktycznie nieudokumentowane)?

EDYTOWANIE Uwaga: kompilator języka C# sam to robi, więc nie jest tak, że istnieje dobry powód, aby go na nas zmusić. Na przykład, jeśli masz .NET 4.5 beta, demontować poniższy kod i sprawdzić ich realizację: (blok wyjątek dodany wewnętrznie)

public static async Task<bool> TestAsync(int ms) 
{ 
    var local = ms/1000; 
    Console.WriteLine("In async call, before await " + local.ToString() + "-second delay."); 
    await System.Threading.Tasks.Task.Delay(ms); 
    Console.WriteLine("In async call, after await " + local.ToString() + "-second delay."); 

    Console.WriteLine(); 
    Console.WriteLine("Press any key to continue."); 
    Console.ReadKey(false); 
    return true; 
} 

Odpowiedz

7

O ile mogę powiedzieć, że nie można tego zrobić w .NET 4.0. Jedynym sposobem na utworzenie ciała metody bez przy użyciu ILGenerator jest użycie MethodBuilder.CreateMethodBody, ale to nie pozwala ustawić informacji dotyczących obsługi wyjątków. I ILGenerator wymusza instrukcję leave, o którą pytasz.

Jeśli jednak .NET 4.5 jest dla ciebie opcją (wydaje się być), spójrz na MethodBuilder.SetMethodBody. To pozwala ci samemu stworzyć IL, ale nadal przechodzić przez informacje o obsłudze wyjątków. Możesz zawinąć to w niestandardową własną klasę ILGenerator, stosując metody Emit przyjmując argument OpCode i czytając OpCode.Size i OpCode.Value, aby uzyskać odpowiednie bajty.

Oczywiście zawsze jest Mono.Cecil, ale to prawdopodobnie wymaga większych zmian w kodzie, który już napisałeś.

Edytuj: you appear to have already figured this out yourself, ale pozostawiłeś to pytanie otwarte. Możesz wysyłać odpowiedzi na swoje własne pytania i akceptować je, jeśli samodzielnie je wymyśliłeś. To dałoby mi znać, że nie powinienem tracić czasu na poszukiwanie, a które pozwoliłoby innym osobom z tym samym pytaniem wiedzieć, co robić.

+0

Dzięki za odpowiedź. Zostawiłem to otwarte, ponieważ nie byłem pewien, czy rozwiązanie, które znalazłem (MethodBuilder.SetMethodBody) faktycznie będzie działać dla mnie - nie ma bardzo opisowej dokumentacji. Dzięki jeszcze raz! – aboveyou00