Sign in to follow this  
Trillian

[.net] Reflection.emit load enumerant

Recommended Posts

Hello! Some googling did not help me find the correct way to load an enumerant's value into the evaluation stack. Here's what I'm doing :
// The following should emit the equivalent of
// "Console.ReadKey(true).Key == ConsoleKey.Y"
IlGenerator.Emit(OpCodes.Ldc_I4_1);
IlGenerator.Emit(OpCodes.Call, typeof(Console).GetMethod("ReadKey", new Type[] { typeof(bool) }));
IlGenerator.Emit(OpCodes.Call, typeof(ConsoleKeyInfo).GetMethod("get_Key", Type.EmptyTypes));
IlGenerator.Emit(OpCodes.Ldsfld, typeof(ConsoleKey).GetField("Y")); // Causes an exception when JIT compiling the opcode
IlGenerator.Emit(OpCodes.Ceq);

Running the program, I get the following exception: System.MissingFieldException: Missing field : 'System.ConsoleKey.Y'. At first I tought that I hadn't referenced the System assembly, but Console.WriteLine works just fine. So I guess must be using a bad OpCode for what I'm trying to do. Any ideas as to what I'm doing wrong? Oh and BTW, how can I add assembly references to a dynamic assembly built with AssemblyBuilder? Thanks!

Share this post


Link to post
Share on other sites
Hello Trillian,

"Oh and BTW, how can I add assembly references to a dynamic assembly built with AssemblyBuilder?"

Never managed to do that directly; I noticed references are added automatically. Since types contain also owner assembly informations, it's a step the il generation performs on its own.

As for your first problem, it's tricky, since enums are actually classes wrapping value types with static public readonly members.


.class private auto ansi sealed TestEnum
extends [mscorlib]System.Enum
{
.field public static literal valuetype TestEnum A = uint32(10)

.field public static literal valuetype TestEnum B = uint32(11)

.field public specialname rtspecialname uint32 value__

}


A dirty solution would be looking up the actual value behind the enum, and emitting checks for that number.

I have the feeling the C# compiler already does that somehow.



.maxstack 2
.locals init (
[0] valuetype TestEnum val,
[1] bool CS$4$0000)
L_0000: nop
L_0001: ldc.i4.s 10
L_0003: stloc.0
L_0004: ldloc.0
L_0005: ldc.i4.s 10
L_0007: ceq
L_0009: ldc.i4.0
L_000a: ceq
L_000c: stloc.1
L_000d: ldloc.1
L_000e: brtrue.s L_001d




It's interesting to see how Enum is a class, yet there's a valuetype modifier in front of the local declaration. I have played with IL extensively, but it looks I missed Enums along the way.

Share this post


Link to post
Share on other sites
Hey Rainweaver, thanks for your input.

First, thanks for the info about the referenced assemblies. Its nice that its done automatically.

Following what you said, I tried to compile the following with MSVC# and check its output:

static void Test()
{
if (Console.ReadKey(true).Key == ConsoleKey.Y) Console.WriteLine("foo");
}

It produces the following:
.method private hidebysig static void Test() cil managed
{
.maxstack 2
.locals init (
[0] valuetype [mscorlib]System.ConsoleKeyInfo CS$0$0000,
[1] bool CS$4$0001)
L_0000: nop
L_0001: ldc.i4.1
L_0002: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey(bool)
L_0007: stloc.0 // ReadKey result stored in a local
L_0008: ldloca.s CS$0$0000 // Address of the local loaded
L_000a: call instance valuetype [mscorlib]System.ConsoleKey [mscorlib]System.ConsoleKeyInfo::get_Key()
L_000f: ldc.i4.s 0x59 // ConsoleKey.Y value inlined by the compiler
L_0011: ceq
L_0013: ldc.i4.0
L_0014: ceq
L_0016: stloc.1
L_0017: ldloc.1
L_0018: brtrue.s L_0025
L_001a: ldstr "foo"
L_001f: call void [mscorlib]System.Console::WriteLine(string)
L_0024: nop
L_0025: ret
}


It looks like the MSVC# compiler inlines the value of the enumerant, so chances are there's no other way to do it.


But checking that code lead me to notice something else:
L_0007: stloc.0
L_0008: ldloca.s CS$0$0000
The compiler caches the result of ReadKey in a local variable and takes the address of the local as the this pointer to the get_Key call. That seems to be the logical thing to do, but in my code I didn't think about that and used the result of the ReadKey (the ConsoleKeyInfo valuetype) as the this pointer in my get_Key call (instead of a pointer to it). Surprisingly, it worked fine. Is it fine to do it like so or should I do as MSVC#?

Share this post


Link to post
Share on other sites
You're welcome. :)

Is that release or debug? I suppose it's debug since I see two nops in the stream.

I assume that additional stloc / ldloc is for debugging purposes. I think your method is just fine, I usually do the same and never had problems.

Share this post


Link to post
Share on other sites
You're right, its a debug build.

As for the stloc/ldloc, its actually a stloc/ldloca, which is quite different.

In the MSVC# case, the address of the structure is passed as the "this" parameter. In my case, the whole structure seems to be passed as the "this" parameter, which looks weird to me.

If we can compare OpCodes.Call with the -> operator in C++ (I know its not an accurate comparison), then it would be like:

// MSVC#:
ConsoleKeyInfo cki = Console.ReadKey(true);
(&cki)->get_Key(); // The "this" argument is a pointer
// Me:
ConsoleKeyInfo cki = Console.ReadKey(true);
(cki)->get_Key(); // The "this" argument is a value type!?


What I mean is, if ConsoleKeyInfo.get_Key expects a pointer as a first argument and I pass the value-type itself, won't I get into trouble?

Share this post


Link to post
Share on other sites
You are right, I didn't realize ConsoleKeyInfo is a struct. Quite different indeed; the compiler is doing the right thing.

1. Calls to an instance (or virtual) method must push that instance reference before any of the user-visible arguments. The instance reference must not be a null reference. The signature carried in the metadata does not contain an entry in the parameter list for the this pointer; instead, it uses a bit to indicate whether the method requires passing the this pointer. (Taken from MSDN, OpCodes.Call)

So I believe the instance reference in this case is represented by the struct address loaded on the stack.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this