1

I'm trying to write a class that's to be used to trigger a call to a method from an arbitrary event but I'm stuck as I simply cannot figure out a way to reference 'this' from emitted MSIL code.

This example should describe what I'm looking for:

class MyEventTriggeringClass
{ 
    private object _parameter;

    public void Attach(object source, string eventName, object parameter)
    {
        _parameter = parameter;
        var e = source.GetType().GetEvent(eventName);
        if (e == null) return;
        hookupDelegate(source, e);
    }

    private void hookupDelegate(object source, EventInfo e)
    {
        var handlerType = e.EventHandlerType;
        // (omitted some validation here)
        var dynamicMethod = new DynamicMethod("invoker",
                  null,
                  getDelegateParameterTypes(handlerType), // (omitted this method in this exmaple)
                  GetType());
        var ilgen = dynamicMethod.GetILGenerator();
        var toBeInvoked = GetType().GetMethod(
            "invokedMethod", 
            BindingFlags.NonPublic | BindingFlags.Instance);
        ilgen.Emit(OpCodes.Ldarg_0); // <-- here's where I thought I could push 'this' (failed)
        ilgen.Emit(OpCodes.Call, toBeInvoked);
        ilgen.Emit(OpCodes.Ret);
        var sink = dynamicMethod.CreateDelegate(handlerType);
        e.AddEventHandler(source, sink);
    }

    private void invokedMethod()
    {
        Console.WriteLine("Value of _parameter = " + _parameter ?? "(null)"); 
        // output is always "(null)"
    }
}

Here's an xample how I envision the class being used:

var handleEvent = new MyEventTriggeringClass();
handleEvent.Attach(someObject, "SomeEvent", someValueToBePassedArround);

(Please note that the above example is quite pointless. I just try to describe what I'm looking for. My final goal here is to be able to trigger a call to an arbitrary method whenever an arbitrary event fires. I'll use that in a WPF projekt where I try to use 100% MVVM but I've stumbled upon one of the [seemingly] classic breaking points.)

Anyway, the code "works" so far as it successfully invoked the "invokedMethod" when the arbitrary event fires but 'this' seems to be an empty object (_parameter is always null). I have done some research but simply cannot find any good examples where 'this' is properly passed to a method being called from within a dynamic method like this.

The closest example I've found is THIS ARTICLE but in that example 'this' can be forced to the dynamic method since it's called from the code, not an arbitrary event handler.

Any suggestions or hints would be very appreciated.

Community
  • 1
  • 1
Jonas Rembratt
  • 1,550
  • 3
  • 17
  • 39
  • Do not use global `_parameter` and pass custom `EventArgs` to your method as a parameter. – Karel Frajták Oct 25 '11 at 12:49
  • If you look closely you'll realize that the targeted method ('invokedMethod') will be called from the arbitrary event handler which is dynamically built. The event handler can therefore be anything like, say, 'MouseDown' or 'SelectedChanged'. The event handler will be passed EventArgs (or some descendant thereof) which are irrelevant and quite useless in this scenario. I just need to be notified when the event is fired and then use a bunch of values ("parameter" in this example) and there's simply no way to pass it down the call chain. That's why I need the 'this' value in the target method. – Jonas Rembratt Oct 25 '11 at 13:07
  • Are you trying to create events as first class citizens in .NET or are you trying to create some sort of message bus? – Enigmativity Oct 25 '11 at 13:22
  • I have two goals here really. The primary goal is to be able to catch any event being fired in a WPF user interface and use that to to write values to an underlying view model data context. The secondary goal is to learn. – Jonas Rembratt Oct 25 '11 at 13:25
  • Can you write the code of the event handler in C# (not using IL)? Why are you assigning the `_parameter` value in `Attach` method? Can you add comments to your code? Thanks – Karel Frajták Oct 25 '11 at 13:40
  • No I cannot write the event handler in C#. The reason is I need the class to be able to listen to _any_ event so the event handler signature will vary with the event. The only solution (that I know of) is to build a dynamic method using MSIL via ILGenerator. – Jonas Rembratt Oct 25 '11 at 14:23
  • `DynamicMethod`s are static, so `this` isn't passed into the method and `Ldarg_0` is going to load the first actual argument (the sender). – kvb Oct 25 '11 at 16:58
  • Kvb: Yes, my research confirms that Ldarg_0 will push the event handler's first argument onto the stack but are you actually saying it's impossible to call instance methods from IL? – Jonas Rembratt Oct 25 '11 at 18:17
  • So you don't need to access the event's parameters in the invoked method? Only `parameter`? – svick Oct 25 '11 at 20:02
  • svick: That's correct. I'm only interested in the timing of the fired event, not the arguments it receives. – Jonas Rembratt Oct 25 '11 at 20:03
  • @Jonas - you can of course call instance methods from IL; however, your `DynamicMethod` will not itself be an instance method of your class, it will be a static method. Therefore, you would need to pass in an instance of your class via some other mechanism (which would be tricky with your current approach). – kvb Oct 25 '11 at 22:39
  • kvb: As long as you can pass an instance to the DynamicMethod it is in effect an instance method. Its first argument wil always be its implicit 'this' value. This is done by passing the instance as the 'target' parameter of `MethodInfo.CreateDelegate()`. Please try the example I provided for clarity. – Jonas Rembratt Oct 25 '11 at 22:58

4 Answers4

2

Because of the way variance on delegates works in .Net, you can write the code in C# without using codegen:

private void InvokedMethod(object sender, EventArgs e)
{
    // whatever
}

private MethodInfo _invokedMethodInfo =
    typeof(MyEventTriggeringClass).GetMethod(
        "InvokedMethod", BindingFlags.Instance | BindingFlags.NonPublic);

private void hookupDelegate(object source, EventInfo e)
{
    Delegate invokedMethodDelegate = 
        Delegate.CreateDelegate(e.EventHandlerType, this, _invokedMethodInfo);
    e.AddEventHandler(source, invokedMethodDelegate);
}

To explain, let's say you have some event that follows the standard event pattern, that is, return type is void, first parameter is object and second parameter is EventArgs or some type derived from EventArgs. If you have that and InvokeMethod defined as above, you can write someObject.theEvent += InvokedMethod. This is allowed because it is safe: you know the second parameter is some type that can act as EventArgs.

And the code above is basically the same, except using reflection when given the event as EventInfo. Just create a delegate of the correct type that references our method and subscribe to the event.

svick
  • 236,525
  • 50
  • 385
  • 514
  • Very good answer (voted it up) and you're right of course: As long as the event in question follows the default signature (object, EventArgs) it should work fine. I honestly don't know if _all_ events in the different .NET frameworks adhers to this signature but I have to assume some events in non-.NET frameworks doesn't so I still have a need to hook up dynamically built event handlers. – Jonas Rembratt Oct 25 '11 at 18:10
  • I have never encountered an event that wouldn't follow the standard pattern, but of course it's possible to create them. But I think you don't have to care about them, unless you actually encounter one. – svick Oct 25 '11 at 18:33
  • To be perfectly honest I think I could get by in at least 95% of the scenarios I'm planning for, maybe even 100% but I would still like to know how to call an instance member via IL. I simply find it hard to leave any stone unturned and feel I need to master codegen. Thanks anyway. – Jonas Rembratt Oct 25 '11 at 19:24
1

If you're sure you want to go with the codegen way, possibly because you want to support non-standard events too, you could do it like this:

Whenever you want to attach to an event, create a class that has a method that matches the event's delegate type. The type will also have a field that holds the passed-in parameter. (Closer to your design would be a field that holds a reference to the this instance of MyEventTriggeringClass, but I think it makes more sense this way.) This field is set in the constructor.

The method will call invokedMethod, passing parameter as a parameter. (This means invokedMethod has to be public and can be made static, if you don't have another reason to keep in non-static.)

When we're done creating the class, create an instance of it, create a delegate to the method and attach that to the event.

public class MyEventTriggeringClass
{
    private static readonly ConstructorInfo ObjectCtor =
        typeof(object).GetConstructor(Type.EmptyTypes);

    private static readonly MethodInfo ToBeInvoked =
        typeof(MyEventTriggeringClass)
            .GetMethod("InvokedMethod",
                       BindingFlags.Public | BindingFlags.Static);

    private readonly ModuleBuilder m_module;

    public MyEventTriggeringClass()
    {
        var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
            new AssemblyName("dynamicAssembly"),
            AssemblyBuilderAccess.RunAndCollect);

        m_module = assembly.DefineDynamicModule("dynamicModule");
    }

    public void Attach(object source, string @event, object parameter)
    {
        var e = source.GetType().GetEvent(@event);
        if (e == null)
            return;
        var handlerType = e.EventHandlerType;

        var dynamicType = m_module.DefineType("DynamicType" + Guid.NewGuid());

        var thisField = dynamicType.DefineField(
            "parameter", typeof(object),
            FieldAttributes.Private | FieldAttributes.InitOnly);

        var ctor = dynamicType.DefineConstructor(
            MethodAttributes.Public, CallingConventions.HasThis,
            new[] { typeof(object) });

        var ctorIL = ctor.GetILGenerator();
        ctorIL.Emit(OpCodes.Ldarg_0);
        ctorIL.Emit(OpCodes.Call, ObjectCtor);
        ctorIL.Emit(OpCodes.Ldarg_0);
        ctorIL.Emit(OpCodes.Ldarg_1);
        ctorIL.Emit(OpCodes.Stfld, thisField);
        ctorIL.Emit(OpCodes.Ret);

        var dynamicMethod = dynamicType.DefineMethod(
            "Invoke", MethodAttributes.Public, typeof(void),
            GetDelegateParameterTypes(handlerType));

        var methodIL = dynamicMethod.GetILGenerator();
        methodIL.Emit(OpCodes.Ldarg_0);
        methodIL.Emit(OpCodes.Ldfld, thisField);
        methodIL.Emit(OpCodes.Call, ToBeInvoked);
        methodIL.Emit(OpCodes.Ret);

        var constructedType = dynamicType.CreateType();

        var constructedMethod = constructedType.GetMethod("Invoke");

        var instance = Activator.CreateInstance(
            constructedType, new[] { parameter });

        var sink = Delegate.CreateDelegate(
            handlerType, instance, constructedMethod);

        e.AddEventHandler(source, sink);
    }

    private static Type[] GetDelegateParameterTypes(Type handlerType)
    {
        return handlerType.GetMethod("Invoke")
                          .GetParameters()
                          .Select(p => p.ParameterType)
                          .ToArray();
    }

    public static void InvokedMethod(object parameter)
    {
        Console.WriteLine("Value of parameter = " + parameter ?? "(null)");
    }
}

This is still doesn't take care of all possible events, though. That's because the delegate of an event can have a return type. That would mean giving a return type to the generated method and returning some value (probably default(T)) from it.

There's (at least) one possible optimization: don't create a new type every time, but cache them. When you try to attach to an event with the same signature as a previous one, use use its class.

svick
  • 236,525
  • 50
  • 385
  • 514
  • Posting as another answer, because this solution is completely different from the previous one. – svick Oct 25 '11 at 21:14
  • svick: Sorry, it seems we've cross posted. I missed your reply while I was answering my own question just now. The problem was that I created the a static delegate without realizing it. If you just pass the dynamically created delegate's target everything works fine. The dynamic method's first argument (loaded by Ldarg_0) then be the 'this' value. Please see my own answer if you're interested. Thanks for helping out. – Jonas Rembratt Oct 25 '11 at 21:23
0

Here is my own version / for my own needs:

    /// <summary>
    /// Corresponds to 
    ///     control.Click += new EventHandler(method);
    /// Only done dynamically, and event arguments are omitted.
    /// </summary>
    /// <param name="objWithEvent">Where event resides</param>
    /// <param name="objWhereToRoute">To which object to perform execution to</param>
    /// <param name="methodName">Method name which to call. 
    ///  methodName must not take any parameter in and must not return any parameter. (.net 4.6 is strictly checking this)</param>
    private static void ConnectClickEvent( object objWithEvent, object objWhereToRoute, string methodName )
    {
        EventInfo eventInfo = null;

        foreach (var eventName in new String[] { "Click" /*WinForms notation*/, "ItemClick" /*DevExpress notation*/ })
        {
            eventInfo = objWithEvent.GetType().GetEvent(eventName);
            if( eventInfo != null )
                break;
        }

        Type objWhereToRouteObjType = objWhereToRoute.GetType();
        var method = eventInfo.EventHandlerType.GetMethod("Invoke");
        List<Type> types = method.GetParameters().Select(param => param.ParameterType).ToList();
        types.Insert(0, objWhereToRouteObjType);

        var methodInfo = objWhereToRouteObjType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null);
        if( methodInfo.ReturnType != typeof(void) )
            throw new Exception("Internal error: methodName must not take any parameter in and must not return any parameter");

        var dynamicMethod = new DynamicMethod(eventInfo.EventHandlerType.Name, null, types.ToArray(), objWhereToRouteObjType);

        ILGenerator ilGenerator = dynamicMethod.GetILGenerator(256);
        ilGenerator.Emit(OpCodes.Ldarg_0);
        ilGenerator.EmitCall(OpCodes.Call, methodInfo, null);
        ilGenerator.Emit(OpCodes.Ret);

        var methodDelegate = dynamicMethod.CreateDelegate(eventInfo.EventHandlerType, objWhereToRoute);
        eventInfo.AddEventHandler(objWithEvent, methodDelegate);
    } //ConnectClickEvent
TarmoPikaro
  • 4,723
  • 2
  • 50
  • 62
0

I'm gonna go ahead and answer my own question here. The solution was very simple once I realized what the real problem was: Specifying the event handler's instance/target. This is done by adding an argument to MethodInfo.CreateDelegate().

If you're interested, here's a simple example you can cut'n'paste into a console app and try it out:

class Program
{
    static void Main(string[] args)
    {
        var test = new MyEventTriggeringClass();
        var eventSource = new EventSource();
        test.Attach(eventSource, "SomeEvent", "Hello World!");
        eventSource.RaiseSomeEvent();
        Console.ReadLine();
    }
}

class MyEventTriggeringClass
{
    private object _parameter;

    public void Attach(object eventSource, string eventName, object parameter)
    {
        _parameter = parameter;
        var sink = new DynamicMethod(
            "sink",
            null,
            new[] { typeof(object), typeof(object), typeof(EventArgs) },
            typeof(Program).Module);

        var eventInfo = typeof(EventSource).GetEvent("SomeEvent");

        var ilGenerator = sink.GetILGenerator();
        var targetMethod = GetType().GetMethod("TargetMethod", BindingFlags.Instance | BindingFlags.Public, null, new Type[0], null);
        ilGenerator.Emit(OpCodes.Ldarg_0); // <-- loads 'this' (when sink is not static)
        ilGenerator.Emit(OpCodes.Call, targetMethod);
        ilGenerator.Emit(OpCodes.Ret);

        // SOLUTION: pass 'this' as the delegate target...
        var handler = (EventHandler)sink.CreateDelegate(eventInfo.EventHandlerType, this);
        eventInfo.AddEventHandler(eventSource, handler);
    }

    public void TargetMethod()
    {
        Console.WriteLine("Value of _parameter = " + _parameter);
    }
}

class EventSource
{
    public event EventHandler SomeEvent;

    public void RaiseSomeEvent()
    {
        if (SomeEvent != null)
            SomeEvent(this, new EventArgs());
    }
}

So, thanks for your comments and help. Hopefully someone learned something. I know I did.

Cheers

Jonas Rembratt
  • 1,550
  • 3
  • 17
  • 39
  • You're (indirectly) using the fact that when creating a delegate to a static method like this, you can treat it as an instance method where the first parameter is the “instance”. Interesting. – svick Oct 25 '11 at 22:08