Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IL Emit for invoking a delegate instance?

Basically, I'm accepting an event name as a string, to get the EventInfo. Then, I'm discovering the event handler type and event argument type using reflection, creating a new delegate of that type (myEventHandler), and hooking it up with the event. When ever myEventHandler is invoked, I need to downcast and pass the arguments to the handler.

My code is as below. The 'handler' needs to be invoked via myEventHandler, when ever 'd' is invoked. I need to have some Reflection emit code there where I put ???. Any thoughts?

EventHandler handler = delegate(object sender, EventArgs eventArgs)
{
    //something will happen here                                
};

Type[] typeArgs = { typeof(object), derivedEventArgsType };

DynamicMethod myEventHandler = new DynamicMethod("", typeof(void), typeArgs);
var ilgen = myEventHandler.GetILGenerator();

//What should be the IL code here to 
//cast derviedEventArgs to EventArgs and
//invoke the 'handler' above??????
ilgen.Emit(OpCodes.Pop);
ilgen.Emit(OpCodes.Ret);



Delegate d = dynamic.CreateDelegate(derviedEventHandlerType);

//addMethod is the add MethodInfo for an Event
addMethod.Invoke(target, new object[] { d });

Edit: Based on observations via Reflector.

The reflector generated code for a manually coded scenario is

.method public hidebysig instance void <Main>b__1(object sender, class ConsoleApplication2.MyEventArgs e) cil managed
{
    .maxstack 8
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: ldfld class [mscorlib]System.EventHandler ConsoleApplication2.Program/<>c__DisplayClass3::handler
    L_0007: ldarg.1 
    L_0008: ldarg.2 
    L_0009: callvirt instance void [mscorlib]System.EventHandler::Invoke(object, class [mscorlib]System.EventArgs)
    L_000e: nop 
    L_000f: ret 
}

And this is what I tried based on that.

ilgen.Emit(OpCodes.Nop); 
ilgen.Emit(OpCodes.Ldarg_0);
ilgen.Emit(OpCodes.Ldfld,eh.GetType().GetField("handler"));
ilgen.Emit(OpCodes.Ldarg_1);
ilgen.Emit(OpCodes.Ldarg_2);
ilgen.EmitCall(OpCodes.Callvirt,eh.handler.Method,
               new Type[]{ typeof(object), typeof(EventArgs) });
ilgen.Emit(OpCodes.Nop);
ilgen.Emit(OpCodes.Ret);

But this is causing a run time error:

'Calling convention must be varargs'

Probably I'm missing something, need to have a better look into IL.

like image 541
amazedsaint Avatar asked Nov 28 '09 18:11

amazedsaint


2 Answers

OK - this might help; it generates the IL to switch between delegate types, as long as they match the standard pattern. It adds a castclass only when necessary (so if you are going from a MouseEventArgs to an EventArgs it isn't necessary, but in the reverse direction it is). Since you are clearly working with reflection, I haven't used generics (which would make things harder).

The cheeky bit is that instead of using a capture class, it pretends the method belongs to the data I would capture, and uses the state as arg0. I can't decide if that makes it evil or clever, so I'll go with "clevil".

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;
using System.Windows.Forms;

class Program {
    static ParameterInfo[] VerifyStandardHandler(Type type) {
        if (type == null) throw new ArgumentNullException("type");
        if (!typeof(Delegate).IsAssignableFrom(type)) throw new InvalidOperationException();
        MethodInfo sig = type.GetMethod("Invoke");
        if (sig.ReturnType != typeof(void)) throw new InvalidOperationException();
        ParameterInfo[] args = sig.GetParameters();
        if (args.Length != 2 || args[0].ParameterType != typeof(object)) throw new InvalidOperationException();
        if (!typeof(EventArgs).IsAssignableFrom(args[1].ParameterType)) throw new InvalidOperationException();
        return args;
    }
    static int methodIndex;
    static Delegate Wrap(Delegate value, Type type) {
        ParameterInfo[] destArgs = VerifyStandardHandler(type);
        if (value == null) return null; // trivial
        if (value.GetType() == type) return value; // already OK
        ParameterInfo[] sourceArgs = VerifyStandardHandler(value.GetType());
        string name = "_wrap" + Interlocked.Increment(ref methodIndex);
        Type[] paramTypes = new Type[destArgs.Length + 1];
        paramTypes[0] = value.GetType();
        for (int i = 0; i < destArgs.Length; i++) {
            paramTypes[i + 1] = destArgs[i].ParameterType;
        }
        DynamicMethod dyn = new DynamicMethod(name, null, paramTypes);
        MethodInfo invoker = paramTypes[0].GetMethod("Invoke");
        ILGenerator il = dyn.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Ldarg_2);
        if (!sourceArgs[1].ParameterType.IsAssignableFrom(destArgs[1].ParameterType)) {
            il.Emit(OpCodes.Castclass, sourceArgs[1].ParameterType);
        }
        il.Emit(OpCodes.Call, invoker);
        il.Emit(OpCodes.Ret);
        return dyn.CreateDelegate(type, value);
    }
    static void Main() {
        EventHandler handler = delegate(object sender, EventArgs eventArgs) {
            Console.WriteLine(eventArgs.GetType().Name);
        };
        MouseEventHandler wrapper = (MouseEventHandler)Wrap(handler, typeof(MouseEventHandler));
        MouseEventArgs ma = new MouseEventArgs(MouseButtons.Left, 1, 1, 1, 1);
        wrapper(new object(), ma);

        EventHandler backAgain = (EventHandler)Wrap(wrapper, typeof(EventHandler));
        backAgain(new object(), ma);
    }
}

Obviously you still need to generate a delegate to the event using regular methods (Delegate.CreateDelegate etc), but you can then wrap it to an EventHandler, or the reverse.

like image 134
Marc Gravell Avatar answered Nov 08 '22 05:11

Marc Gravell


It turns out I was vastly over-complicating things! Barry Kelly had the right idea:

static T CastDelegate<T>(Delegate src)
    where T : class
{
    return (T)(object)Delegate.CreateDelegate(
        typeof(T),
        src.Target,
        src.Method,
        true); // throw on fail
}

That works for my test cases.

like image 22
Marc Gravell Avatar answered Nov 08 '22 07:11

Marc Gravell