5
Food obj = ...;
ILGenerator gen = (...).GetILGenerator();
gen.Emit( ?? obj ?? ); // replace this 
gen.Emit(OpCodes.Call, typeof(Person).GetMethod("Eat"));

It's apparently not possible to cleanly push obj onto the evaluation stack, but I am open to ugly hacks which might compromise e.g. portability. ModuleBuilder.DefineInitializedData allows one to store a System.Byte[] in the .sdata. Any ideas?

Edit: the generated method is being emitted as part of a new assembly.

casperOne
  • 73,706
  • 19
  • 184
  • 253
shivak
  • 51
  • 5

2 Answers2

1
object o = ...;
Func<object> sneaky = () => o;
gen.Emit(OpCodes.Call, sneaky.Method);

On a side note, make sure you can't use System.Linq.Expressions for your purpose. Here's a section of my code in the ANTLR project before and after:

Before. Note that there's a bug in this (can't find the mailing list post about it) that I didn't have to find because the switch to "After" corrected it as a side effect.

private static Func<object, object> BuildAccessor(MethodInfo method)
{
    DynamicMethod dm = new DynamicMethod(method.DeclaringType.Name + method.Name + "MethodAccessor", typeof(object), new Type[] { typeof(object) }, method.DeclaringType);
    var gen = dm.GetILGenerator();

    if (!method.IsStatic)
    {
        gen.Emit(System.Reflection.Emit.OpCodes.Ldarg_0);
        gen.Emit(System.Reflection.Emit.OpCodes.Castclass, method.DeclaringType);
    }

    if (method.IsVirtual && !method.IsFinal)
        gen.EmitCall(System.Reflection.Emit.OpCodes.Callvirt, method, null);
    else
        gen.EmitCall(System.Reflection.Emit.OpCodes.Call, method, null);

    if (method.ReturnType.IsValueType)
        gen.Emit(System.Reflection.Emit.OpCodes.Box, method.ReturnType);

    gen.Emit(System.Reflection.Emit.OpCodes.Ret);
    return (Func<object, object>)dm.CreateDelegate(typeof(Func<object, object>));
}

private static Func<object, object> BuildAccessor(FieldInfo field)
{
    DynamicMethod dm = new DynamicMethod(field.DeclaringType.Name + field.Name + "FieldAccessor", typeof(object), new Type[] { typeof(object) }, field.DeclaringType);

    var gen = dm.GetILGenerator();
    if (field.IsStatic)
    {
        gen.Emit(System.Reflection.Emit.OpCodes.Ldsfld, field);
    }
    else
    {
        gen.Emit(System.Reflection.Emit.OpCodes.Ldarg_0);
        gen.Emit(System.Reflection.Emit.OpCodes.Castclass, field.DeclaringType);
        gen.Emit(System.Reflection.Emit.OpCodes.Ldfld, field);
    }

    if (field.FieldType.IsValueType)
        gen.Emit(System.Reflection.Emit.OpCodes.Box, field.FieldType);

    gen.Emit(System.Reflection.Emit.OpCodes.Ret);
    return (Func<object, object>)dm.CreateDelegate(typeof(Func<object, object>));
}

After:

private static Func<object, object> BuildAccessor(MethodInfo method)
{
    ParameterExpression obj = Expression.Parameter(typeof(object), "obj");

    Expression<Func<object, object>> expr =
        Expression.Lambda<Func<object, object>>(
            Expression.Convert(
                Expression.Call(
                    Expression.Convert(obj, method.DeclaringType),
                    method),
                typeof(object)),
            obj);

    return expr.Compile();
}

private static Func<object, object> BuildAccessor(FieldInfo field)
{
    ParameterExpression obj = Expression.Parameter(typeof(object), "obj");

    Expression<Func<object, object>> expr =
        Expression.Lambda<Func<object, object>>(
            Expression.Convert(
                Expression.Field(
                    Expression.Convert(obj, field.DeclaringType),
                    field),
                typeof(object)),
            obj);

    return expr.Compile();
}
Sam Harwell
  • 97,721
  • 20
  • 209
  • 280
  • This results in a MethodAccessException because the locally defined lambda isn't accessible in the new method. – shivak Aug 13 '09 at 21:58
  • That's interesting, because it's exactly what I do in my experimental StringTemplate compiler (and it works there). – Sam Harwell Aug 13 '09 at 23:00
  • Sorry, I meant inaccessible from the new assembly which is emitted. Making a reference type available there seems unreasonable and that's why I initially felt a kludge would be necessary. – shivak Aug 13 '09 at 23:07
  • Obviously you can't new up an object and emit it like that. I assumed it was for a `DynamicMethod`. For what you're talking about, emit the `newobj` instruction, or create a class with a static method/property that you can call to return the object that was created by the emitted assembly's code at runtime. – Sam Harwell Aug 13 '09 at 23:33
  • The program that emits the new assembly terminates, and the new assembly is used separately. So with either method, the problem of transferring the value of the object remains. I hope there is a nicer way than serializing the object as initialized data in the module. – shivak Aug 14 '09 at 00:25
  • There's really not. :\ When you think about it, that's exactly how the C# compiler does it. – Sam Harwell Aug 14 '09 at 00:28
0

I would suggest serializing the object you need, and emitting a call to deserialize it from a resource stream (possibly cached, if you are going to access it frequently).

Doug McClean
  • 14,265
  • 6
  • 48
  • 70