2014-10-30 41 views
5

當我刪除Ldstr "a"Call Console.WriteLine(在Ret之前)時,代碼運行良好,否則調用時會引發InvalidProgramException。這是否意味着需要一個空的評估棧?在異常塊之前需要一個空的評估堆棧嗎?

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

我懷疑是這種情況(在引入新的異常幀之前,評估堆棧必須是空的),但我現在找不到正式的參考。 – 2014-10-30 16:59:01

回答

7

爲了理解你在做什麼,我建議你儘可能使用最少的示例。

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

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

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

之後,您可以使用AssemblyBuilder給定的代碼轉儲到可執行文件。如果完成,ildasm將顯示已生成的內容。

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

正如你所看到的,我們會達到的leave指令,跳到pop。然後,您可以google一下leave,其中指出:

leave指令類似於BR指令,但它可以用來退出一試,過濾器,或而普通分支 指令可以catch塊 只能用在這樣的塊中,以便在其內部傳送控制 。 離開指令清空評估堆棧和 可確保執行相應的最終塊。

但是,爲什麼下面的工作沒有呢?

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

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

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

我懷疑它可能不是「物理限制」,而是一個驗證問題。讓我們運行peverify ourapp.exe,看看我們得到:

[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 

在這一點上,你可能會喜歡,笏?用一點點的谷歌搜索,你可以拿出一個錯誤代碼0x801318A9。通過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; 

現在,這是很酷,但如果你是令人討厭的,你可能想知道爲什麼不計算堆棧必須是空的?

爲此,您可能需要查看ECMA C# and Common Language Infrastructure Standards。我懷疑你可以從PartitionIII中找到原因CIL.pdf

+0

'peverify',太棒了! – Chris 2017-10-20 21:22:51