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