When I remove Ldstr "a"
and Call Console.WriteLine
(before Ret
), the code runs fine, otherwise an InvalidProgramException
is thrown upon invocation. Does this mean that an empty evaluation stack is required?
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();
}
}
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With