mit Reflection.Emit eine Event-Umlenkung bauen

  • VB.NET

Es gibt 9 Antworten in diesem Thema. Der letzte Beitrag () ist von hal2000.

    mit Reflection.Emit eine Event-Umlenkung bauen

    Hi!

    Ich hab hier einen Code aussm Internet, wo mit Reflection.Emit ein Delegat gebastelt wird, der zu einem beliebiges Event passt, und in dessen Methode eine annere Methode aufgerufen wird. Der Typ des Events wird als Argument gegeben, und ebenso ein MethodInfo der aufzurufenden Methode.

    VB.NET-Quellcode

    1. ''' <summary>
    2. ''' erzeugt mit Reflection.Emit einen aufruffähigen Delegaten, der das angegebene Event abonnieren könnte, und der dann methodToInvoke aufruft.
    3. ''' </summary>
    4. Public Shared Function CreateDelegate(ByVal eventHandlerType As Type, ByVal method As MethodInfo, ByVal methodOwner As Object) As [Delegate]
    5. 'Get the eventHandlerType signature
    6. Dim handlerMethod = eventHandlerType.GetMethod("Invoke")
    7. If handlerMethod.ReturnParameter.ParameterType IsNot GetType(System.Void) Then
    8. Throw New ApplicationException("Delegate has a return type. This only supprts event handlers that are void")
    9. End If
    10. Dim handlerParams As ParameterInfo() = handlerMethod.GetParameters()
    11. 'Get the list of type of parameters. Please note that we do + 1 because we have to push the object where the method resides i.e methodInvoker parameter
    12. Dim dynamicParamTypes As Type() = New Type(handlerParams.Length) {}
    13. dynamicParamTypes(0) = methodOwner.[GetType]()
    14. For i As Integer = 0 To handlerParams.Length - 1
    15. dynamicParamTypes(i + 1) = handlerParams(i).ParameterType
    16. Next
    17. Dim dynamicHandler As New DynamicMethod("", Nothing, dynamicParamTypes)
    18. Dim eventIL As ILGenerator = dynamicHandler.GetILGenerator()
    19. 'load the parameters or everything will just BAM :)
    20. Dim local As LocalBuilder = eventIL.DeclareLocal(GetType(Object()))
    21. eventIL.Emit(OpCodes.Ldc_I4, handlerParams.Length + 1)
    22. eventIL.Emit(OpCodes.Newarr, GetType(Object))
    23. eventIL.Emit(OpCodes.Stloc, local)
    24. 'start from 1 because the first item is the instance. Load up all the arguments
    25. For i As Integer = 1 To handlerParams.Length
    26. eventIL.Emit(OpCodes.Ldloc, local)
    27. eventIL.Emit(OpCodes.Ldc_I4, i)
    28. eventIL.Emit(OpCodes.Ldarg, i)
    29. eventIL.Emit(OpCodes.Stelem_Ref)
    30. Next
    31. eventIL.Emit(OpCodes.Ldloc, local)
    32. 'Load as first argument the instance of the object for the methodToInvoke i.e methodInvoker
    33. eventIL.Emit(OpCodes.Ldarg_0)
    34. 'Now that we have it all set up call the actual method that we want to call for the binding
    35. eventIL.Emit(OpCodes.[Call], method)
    36. 'return
    37. eventIL.Emit(OpCodes.Pop)
    38. eventIL.Emit(OpCodes.Ret)
    39. 'create a delegate from the dynamic method
    40. Return dynamicHandler.CreateDelegate(eventHandlerType, methodOwner)
    41. End Function
    Das Manko: Das MethodInfo kann nur eine Action-Methode sein, also ohne Parameter und ohne Rückgabewert.
    Ich hätte nun gerne eine Verbesserung, dass man einen richtigen EventHandler angeben kann, entweder auch in Form eines MethodInfos, aber mw. auch gerne als Delegat.

    Sinn der Sache ist für Wpf - damit könnte ich einen Event-Umleiter basteln, der Events vom View ins Viewmodel leitet - das geht nämlich derzeit in Wpf nicht.
    Es gibt bereits derlei Lösungen, aber dazu müsste ich extra eine WpfToolkit-Dll mitliefern, und die ist auch nicht so komfortabel und performant wie das Teil, an dem ich grad sitze.

    Also mein Problem ist "einfach" zeile#36, wo das MethodInfo aufgerufen wird.
    Wie wäre das zu gestalten, wenn das MethodInfo nicht eine leere Action beschreibt, sondern etwa eine Action(Of MouseEventArgs), oder gleich die ganze MouseEventHandler-Signatur?
    Und das eben flexibel, also dassich auch einen KeyDownEventhandler angeben kann, und der ruft mit den richtigen Parametern auf?

    Wenn sich jmd. da reinknien mag - ich kann auch eine TestSolution aufsetzen, mit der bisherigen Funktionalität, und auch der neuen Funktionalität, unter Auslassung dieses fehlenden Details.
    Falls gewünscht - die TestSolution in Wpf oder in Winforms?
    Also ich habe mit Emit noch nicht viel gearbeitet. Aber erlaubt Emit das Angeben einer MethodInfo-Instanz nicht, wenn die Signatur Name(Object, EventArgs) ist?
    Ich verstehe auch gerade nicht ganz, wieso Du die Argumente so komisch lädtst. Ich verstehe eigentlich nicht, warum Du Argumente zur Zeit überhaupt lädst. Denn laut
    Das MethodInfo kann nur eine Action-Methode sein, also ohne Parameter und ohne Rückgabewert.
    kann die Methode im Moment gar keine Argumente haben... oder verstehe ich da etwas nicht?
    Es sollte möglich sein, ein Object und ein EventArgs auf den Stack zu legen, und dann die Methode aufzurufen. Es sollte eigentlich auch möglich sein, einen genaueren EventArgs-Typ anzugeben, damit es auch zusammenpasst.
    Eben wenn Emit das Angeben einer solchen Methode erlaubt.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    was da an Parametern geladen (oder was) wird sind die Argumente der eingehenden Methode.

    Was bisher ausschließlich parameterlos funzt ist das Aufrufen der ausgehenden Methode.

    Wie gesagt: Was da gebaut und in einen Delegaten geschoben wird ist ein Eventhandler, der eine Action aufruft.
    Hi
    spricht was gegen

    VB.NET-Quellcode

    1. Dim delegate As [Delegate] = CreateDelegate(eventHandlerType, target, method)
    2. delegate.DynamicInvoke(parameters)
    ?
    Btw. für den Return-Wert müsste man glaub' ich einfach den Return-Typ der dynamischen Methode setzen und das Pop rauslassen.

    @Niko Ortner: Gemeint war, dass kein Rückgabetyp außer System.Void unterstützt wird.

    Gruß
    ~blaze~
    Ich verstehe den Sinn der Sache nicht - du kannst sowas auch basteln, ohne mit IL zu hantieren (oder ich habe das Vorhaben nicht verstanden). Es soll einen Eventhandler geben, der
    • Beliebige Events annimmt
    • Eine Methode aufruft, die einen Rückgabewert hat


    VB.NET-Quellcode

    1. Event MyEvent1(ByVal arg1 As Object)
    2. Event MyEvent2 As EventHandler(Of String) 'Signatur = (sender As Object, e As String)
    3. AddHandler MyEvent1, AddressOf GenericHandler 'Geht
    4. AddHandler MyEvent2, AddressOf GenericHandler 'Geht auch
    5. 'Das wird oben per Emit zusammengebaut
    6. Private Sub GenericHandler(ParamArray args() As Object)
    7. 'args(1) ist MouseEventArgs oder KeyDownEventArgs oder ...
    8. 'Die Argumente werden ignoriert
    9. MyFunction()
    10. End Sub
    11. Function MyFunction() As Object
    12. 'Diese Funktion wird vom Handler aufgerufen
    13. End Function
    Gruß
    hal2000

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „hal2000“ ()

    Das MethodInfo soll keine Function mit Rückgabewert beschreiben, sondern es soll möglich sein, dass das MethodInfo Argumente entgegennimmt. Kein Rückgabewert

    Weil derzeit beschreibt das MethodInfo eine Action, und in Zukunft solls eine Action(Of T) beschreiben, wobei T eben die EventArgs sein sollten, mit denen dieser DynamicMethod-Delegat aufgerufen wird.
    Na dann reiche doch die Argumente in GenericHandler an MyFunction (oder auch MySub, wenns ne Action Of T sein soll) weiter:

    VB.NET-Quellcode

    1. Private Sub GenericHandler(ParamArray args() As Object)
    2. MySub(args)
    3. End Sub
    4. Sub MySub(ParamArray args() As Object)
    5. 'warum sollte man das weiterreichen? Der Handler oben nimmt doch schon alle Events an!
    6. End Sub


    Übrigens: Mein Aufruf deiner CreateDelegate-Methode feuert ne TargetInvocationException - verwende ich das überhaupt richtig?

    VB.NET-Quellcode

    1. Dim mi As MethodInfo = Me.GetType().GetMethod("NewHandler", BindingFlags.NonPublic Or BindingFlags.Instance)
    2. Dim del As [Delegate] = CreateDelegate(GetType(Action(Of Object)), mi, Me)
    3. del.DynamicInvoke("bla") 'Exception
    4. Private Sub NewHandler()
    5. Console.WriteLine("new handler")
    6. End Sub


    Edit: Ich glaube ich habs verstanden (vergleiche unten "current" und "want") - der IL-Code produziert folgendes:

    VB.NET-Quellcode

    1. Sub MyMethod() 'args of eventHandler Delegate
    2. 'Dim o As New Object(argcount)
    3. 'For i = 0 to argcount - 1
    4. 'o(i) = args(i)
    5. 'Next
    6. 'current: Call method of methodOwner WITHOUT args
    7. 'want: Call method of methodOwner WITH args
    8. 'Return
    9. End Sub

    Stimmt das soweit?
    Gruß
    hal2000

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „hal2000“ ()

    Ich versteh' ehrlich gesagt auch nicht, warum das funktionieren soll. Invoke ist eine Prozedur, die die Parameter direkt entgegennimmt, kein Object-Array. So müsste man doch die MethodInfo selber kapseln und MethodInfo.Invoke aufrufen oder [Delegate].DynamicInvoke oder?
    Was passt dir an [Delegate].DynamicInvoke nicht?

    @hal2000: müsste es nicht Action ohne (Of Object) sein? Deine Methode nimmt keinen Parameter entgegen.

    Gruß
    ~blaze~

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „~blaze~“ ()

    hal2000 schrieb:

    VB.NET-Quellcode

    1. Event MyEvent1(ByVal arg1 As Object)
    2. Event MyEvent2 As EventHandler(Of String) 'Signatur = (sender As Object, e As String)
    sind zwar funktionierende Aufrüfe, aber Bill der Osterhase hat eigentlich den Plan, dass alle Events mit der Signatur Sender, EventArgs-Abkömmling auszustatten sind, da würde ich doch lieber auf MyEventArgs als auf String setzen.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @~blaze~: Das sollte eigentlich egal sein, weil die zusammengebaute Methode die Argumente ignoriert. Die Argumente des Delegaten im ersten Parameter von CreateDelegate sollen / müssen zu dem Event passen, das den Handler-Delegaten später aufruft. Bei diesem Aufruf werden die Argumente ignoriert und die Methode im 2. Parameter wird auf die Instanz im 3. Parameter aufgerufen.

    Ich habs natürlich trotzdem probiert - Ergebnis: Geht nicht. @ErfinderDesRades: müsste mal einen funktionierenden Beispielaufruf posten.

    @RodFromGermany: Stimmt - der Code sollte auch nur als Beispiel dafür dienen, dass der Handler beliebige Event-Signaturen annimmt.
    Gruß
    hal2000