2009-11-28 5 views
5

Grundsätzlich akzeptiere ich einen Ereignisnamen als Zeichenfolge, um die EventInfo zu erhalten. Dann entdecke ich den Event-Handler-Typ und den Event-Argumenttyp mithilfe der Reflektion, indem ich einen neuen Delegaten dieses Typs() erstelle und ihn mit dem Event verknüpfe. Wann immer myEventHandler aufgerufen wird, muss ich Downcast und übergeben Sie die Argumente an den Handler.IL Emit zum Aufrufen einer Delegat-Instanz?

Mein Code ist wie folgt. Der 'Handler' muss über myEventHandler aufgerufen werden, wenn 'd' aufgerufen wird. Ich brauche etwas Reflektionscode dort, wo ich hingehe ???. Irgendwelche Gedanken?

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: Basierend auf Beobachtungen über Reflektor.

Die Reflektor-Code für ein manuell codiertes Szenario erzeugt wird

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

Und das ist, was habe ich versucht, auf dieser Grundlage.

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); 

Aber dies einen Laufzeitfehler verursacht:

'Calling convention must be varargs'

Wahrscheinlich bin ich etwas fehlt, brauchen einen besseren Blick in IL haben.

+2

Der Trick hier ist immer einfach den Code in C# schreiben und reflector/ILDASM verwenden, um die IL zu betrachten. Ich würde schätzen, eine Kombination von Id, Castclass und Callvirt –

+0

Yep zugestimmt. Ich werde diesen Weg nehmen, aber dachte, dass jede Reflexion Ninjas in SO ausstrahlt, kann das schnell darauf hinweisen – amazedsaint

+0

Noch einmal schauen - wo befindet sich "Handler"? relativ zu den Argumenten? Ich denke, es wird ein Schmerz sein, die beiden zusammenzubringen. Es sieht so aus, als ob die C# -Version eine Capture-Klasse verwendet, aber Ihre dynamische Methode zur Zeit ist statisch, so dass Sie keinen Zustand verschieben können ... –

Antwort

5

Es stellt sich heraus, dass ich die Dinge viel zu kompliziert machte! 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 
} 

Das ist für meine Testfälle funktioniert.

5

OK - das könnte helfen; Es generiert die IL, um zwischen den Delegattypen zu wechseln, solange sie dem Standardmuster entsprechen. Es fügt eine Castclass nur bei Bedarf hinzu (wenn Sie also von MouseEventArgs zu EventArgs gehen, ist es nicht notwendig, aber in umgekehrter Richtung). Da Sie eindeutig mit Reflexion arbeiten, habe ich keine Generika verwendet (was die Sache schwieriger machen würde).

Der freche Bit ist, dass stattdessen eine Capture Klasse zu verwenden, es gibt vor, das Verfahren auf die Daten gehören ich erfassen würde, und verwendet den Staat als arg0. Ich kann mich nicht entscheiden, ob das böse oder schlau macht, also werde ich mit "Clevil" gehen.

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); 
    } 
} 

Natürlich müssen Sie noch einen Vertreter zu der Veranstaltung mit regulären Methoden (Delegate.CreateDelegate etc) zu erzeugen, aber Sie können es dann wickeln zu einem EventHandler, oder umgekehrt.

+0

Awesome. Ich muss das noch ausprobieren, aber ich bin mir nicht sicher, ob ich eine bessere Antwort bekommen kann. Daher akzeptiere ich das als Antwort. Danke :) – amazedsaint

+0

Nun, bestätigt. Gelöst, was ich wollte. – amazedsaint