Reflection.Emit 生成動態類型時,異常處理塊 (try/catch/finally) 的構建方式與普通 IL 指令截然不同,需使用 ILGenerator 的三個專用方法:BeginExceptionBlock、BeginCatchBlock、EndExceptionBlock。本文以 Convert.ToInt32 安全轉換為例,完整演示 IL 級異常處理流程。
public static int ConvertToInt32(string str)
{
int num = 0;
try
{
num = Convert.ToInt32(str);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return num;
}
| 方法 | 對應 C# 關鍵字 | 說明 |
|---|---|---|
BeginExceptionBlock() | try | 標記受保護區塊開始,返回用於跳轉的 Label |
BeginCatchBlock(Type) | catch (T) | 開始捕獲塊,參數為異常類型;此時評估棧頂為異常實例 |
EndExceptionBlock() | } (整個 try-catch 結束) | 結束異常處理區塊,含所有 catch/finally |
var typeBuilder = moduleBuilder.DefineType("ExceptionHandler", TypeAttributes.Public);
var methodBuilder = typeBuilder.DefineMethod(
"ConvertToInt32",
MethodAttributes.Public | MethodAttributes.Static,
typeof(int),
new[] { typeof(string) });
var il = methodBuilder.GetILGenerator();
var numLocal = il.DeclareLocal(typeof(int));
// int num = 0
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Stloc, numLocal);
// ---- try 區塊 ----
var tryLabel = il.BeginExceptionBlock(); // 等同 try {
// num = Convert.ToInt32(str)
il.Emit(OpCodes.Ldarg_0); // 參數 str
il.Emit(OpCodes.Call, typeof(Convert).GetMethod("ToInt32", new[] { typeof(string) }));
il.Emit(OpCodes.Stloc, numLocal);
// ---- catch (Exception ex) ----
il.BeginCatchBlock(typeof(Exception)); // 棧頂現為 ex
// Console.WriteLine(ex.Message)
il.Emit(OpCodes.Callvirt, typeof(Exception).GetProperty("Message").GetGetMethod());
il.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }));
// ---- 結束 try-catch ----
il.EndExceptionBlock(); // 等同 }
// return num
il.Emit(OpCodes.Ldloc, numLocal);
il.Emit(OpCodes.Ret);
var type = typeBuilder.CreateType();
assemblyBuilder.Save("DynamicAssembly.dll");
Try Block:
Ldarg_0
Call Convert.ToInt32
Stloc num
Leave -> EndTryLabel
Catch Block (Exception on stack):
Callvirt Exception.get_Message
Call Console.WriteLine
Leave -> EndTryLabel
EndTryLabel:
Ldloc num
Ret
// 多 catch
il.BeginCatchBlock(typeof(FormatException));
// 處理格式錯誤
il.BeginCatchBlock(typeof(OverflowException));
// 處理溢出
il.BeginCatchBlock(typeof(Exception));
// 兜底
// Finally (需在所有 catch 之後,EndExceptionBlock 之前)
il.BeginFinallyBlock();
// 清理代碼
il.EndExceptionBlock();
catch 時棧頂自動放入異常對象,離開前必須消費掉 (調用屬性/方法或 pop),否則驗證失敗。OpCodes.Leave (或 Leave_S),它會自動執行 finally 邏輯;Br 會導致驗證錯誤。peverify.exe DynamicAssembly.dll 檢查 IL 合法性。掌握異常塊發射,才能構建健壯的動態代理、攔截器與編譯器後端。