views:

325

answers:

2
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.

+1  A: 
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();
}
280Z28
This results in a MethodAccessException because the locally defined lambda isn't accessible in the new method.
shivak
That's interesting, because it's exactly what I do in my experimental StringTemplate compiler (and it works there).
280Z28
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
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.
280Z28
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
There's really not. :\ When you think about it, that's exactly how the C# compiler does it.
280Z28
A: 

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