To understand what you're doing, I suggest you to make as minimal example as possible.
ilGen.Emit(OpCodes.Ldstr, "a");
ilGen.BeginExceptionBlock();
ilGen.BeginCatchBlock(typeof(Exception));
ilGen.EndExceptionBlock();
ilGen.Emit(OpCodes.Pop);
ilGen.Emit(OpCodes.Ret);
After that, you can use AssemblyBuilder
to dump the given code into the executable. If that's done, ildasm
will show what has been generated.
// 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
As you can see, we will reach to the leave
instruction which jumps to pop
. You can then google about leave
, which states that:
The leave instruction is similar to the br instruction, but it can be
used to exit a try, filter, or catch block whereas the ordinary branch
instructions can only be used in such a block to transfer control
within it. The leave instruction empties the evaluation stack and
ensures that the appropriate surrounding finally blocks are executed.
However, why doesn't the following work then?
ilGen.Emit(OpCodes.Ldstr, "a");
ilGen.BeginExceptionBlock();
ilGen.BeginCatchBlock(typeof(Exception));
ilGen.EndExceptionBlock();
//ilGen.Emit(OpCodes.Pop);
ilGen.Emit(OpCodes.Ret);
I suspect it might not be "physical limit", but a verification issue. Let's run peverify ourapp.exe
and see what we get:
[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
At this point, you might be like, wat? With a little bit of googling, you can come up with an error code of 0x801318A9. Quick scan through SSCLI2.0 sources:
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;
Now, this is cool, but if you're geeky, you might wonder why does the evaluation stack has to be empty?
For that, you probably want to take a look at ECMA C# and Common Language Infrastructure Standards. I suspect you could find the reason from PartitionIII CIL.pdf