Reflection.Emit.ILGenerator Обработка исключений инструкция «Выйти» - PullRequest
10 голосов
/ 10 марта 2012

Сначала немного справочной информации:

Я делаю компилятор для школьного проекта.Это уже работает, и я прилагаю много усилий, чтобы исправить ошибку и / или оптимизировать ее.Недавно я столкнулся с проблемой, связанной с тем, что я обнаружил, что объект ILGenerator генерирует дополнительную инструкцию leave при вызове любого из следующих методов-членов:

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

Итак, вы запускаете инструкцию tryпри вызове BeginExceptionBlock() добавьте пару предложений catch с BeginCatchBlock(), возможно, добавьте предложение finally с BeginFinallyBlock(), а затем завершите область защищенного кода с помощью EndExceptionBlock().

Методы, которые я перечислил, автоматически генерируют инструкцию leave, переходящую к первой инструкции после инструкции try.Я не хочу этого по двум причинам.Во-первых, потому что он всегда генерирует неоптимизированную инструкцию leave, а не инструкцию leave.s, даже если она разветвляется всего на два байта.И второе, потому что вы не можете контролировать, куда идет инструкция выхода.

Итак, если вы хотите перейти в другое место в вашем коде, вам нужно добавить сгенерированную компилятором локальную переменную, установите ее в зависимостив том месте, где вы хотите перейти внутри оператора try, пусть EndExceptionBlock() автоматически сгенерирует инструкцию leave, а затем сгенерирует оператор switch ниже блока try.ИЛИ, вы можете просто выполнить инструкцию leave или leave.s самостоятельно, прежде чем вызывать один из предыдущих методов, что приведет к появлению лишних и недоступных дополнительных 5 байтов, например:

L_00ca: leave.s L_00e5
L_00cc: leave L_00d1

Оба этиварианты для меня неприемлемы.Есть ли способ предотвратить автоматическую генерацию leave инструкций или любой другой способ указать защищенные области вместо использования этих методов (которые чрезвычайно раздражают и практически не документированы)?

РЕДАКТИРОВАТЬ Примечание:Компилятор C # делает это, так что нет веской причины навязывать это нам.Например, если у вас установлена ​​бета-версия .NET 4.5, разберите следующий код и проверьте его реализацию: (внутренний блок исключений добавлен)

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;
}

1 Ответ

7 голосов
/ 10 марта 2012

Насколько я могу судить, вы не можете сделать это в .NET 4.0.Единственный способ создать тело метода без с использованием ILGenerator - это использовать MethodBuilder.CreateMethodBody, но это не позволяет вам установить информацию об обработке исключений.И ILGenerator заставляет команду leave, о которой вы спрашиваете.

Однако, если .NET 4.5 - вариант для вас (кажется), взгляните на MethodBuilder.SetMethodBody.Это позволяет вам создать IL самостоятельно, но все же пройти через информацию обработки исключений.Вы можете обернуть это в свой собственный ILGenerator -подобный класс, используя методы Emit, принимающие аргумент OpCode и читающие OpCode.Size и OpCode.Value, чтобы получить соответствующие байты.

И, конечно, всегда есть Mono.Cecil , но для этого, вероятно, потребуются более обширные изменения в коде, который вы уже написали.

Редактировать : , который вы видитеВы уже поняли это , но вы оставили этот вопрос открытым.Вы можете оставлять ответы на свои вопросы и принимать их, если вы сами это выяснили.Это дало бы мне знать, что я не должен был тратить время на поиски, и это позволило бы другим людям с таким же вопросом знать, что делать.

...