views:

114

answers:

3

Good day all

I wrote the following method:

private void RegisterEvent(object targetObject, string eventName, string methodName)
{
    EventInfo eventInfo = targetObject.GetType().GetEvent(eventName);
    MethodInfo method = eventInfo.EventHandlerType.GetMethod("Invoke");
    IEnumerable<Type> types = method.GetParameters().Select(param => param.ParameterType);

    DynamicMethod dynamicMethod = new DynamicMethod(eventInfo.EventHandlerType.Name, typeof (void), types.ToArray(), typeof (QueryWindow));
    MethodInfo methodInfo = typeof (QueryWindow).GetMethod(methodName, new[] { typeof (object) });

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

    dynamicMethod.DefineParameter(1, ParameterAttributes.In, "sender");
    dynamicMethod.DefineParameter(2, ParameterAttributes.In, "e");

    // Get an argument exception here
    Delegate methodDelegate = dynamicMethod.CreateDelegate(eventInfo.EventHandlerType, this);
    eventInfo.AddEventHandler(targetObject, methodDelegate);
}

I get ArgumentException with the message

Error binding to target method.

in the line

Delegate methodDelegate = dynamicMethod.CreateDelegate(eventInfo.EventHandlerType, this); 

Could anyone point out on my mistake?

Thanks in advance.

+1  A: 

When calling DynamicMethod.CreateDelegate, you should not pass a target parameter.

Edit:

I think you would also have to make the first parameter = 0, and change the codegen accordingly.

leppie
+4  A: 

dynamicMethod.CreateDelegate(eventInfo.EventHandlerType, this);

The this argument cannot be correct. That references your class, the one that generates the dynamic type. Surely you need targetObject instead.

Hans Passant
That could work too :)
leppie
+1  A: 

Assuming that methodName is a static method of QueryWindow, this should work:

private static void RegisterEvent(object targetObject, string eventName, string methodName)
{
    var eventInfo = targetObject.GetType().GetEvent(eventName);
    var method = eventInfo.EventHandlerType.GetMethod("Invoke");
    var types = method.GetParameters().Select(param => param.ParameterType);

    var methodInfo = typeof(QueryWindow).GetMethod(methodName, new[] { typeof(object) });

    // replaced typeof(void) by null      
    var dynamicMethod = new DynamicMethod(eventInfo.EventHandlerType.Name, null, types.ToArray(), typeof(QueryWindow));

    ILGenerator ilGenerator = dynamicMethod.GetILGenerator(256);

    // Using Ldarg_0 to pass the sender to methodName ; Ldarg_1 to pass the event args
    ilGenerator.Emit(OpCodes.Ldarg_0);
    ilGenerator.EmitCall(OpCodes.Call, methodInfo, null);

    // Added return
    ilGenerator.Emit(OpCodes.Ret); 

    // Removed parameter definition (implicit from DynamicMethod constructor)

    // Removed the target argument
    var methodDelegate = dynamicMethod.CreateDelegate(eventInfo.EventHandlerType);
    eventInfo.AddEventHandler(targetObject, methodDelegate);
}

Edit :

Since you can use .NET 3.5, you should create an expression tree. Here is another solution:

public class QueryWindow
{
    public void RegisterEvent(object targetObject, string eventName, string methodName)
    {
        var eventInfo = targetObject.GetType().GetEvent(eventName);
        var sender = Expression.Parameter(typeof (object), "sender");
        var e = Expression.Parameter(typeof (EventArgs), "e");
        var body = Expression.Call(Expression.Constant(this), methodName, null, e);
        var lambda = Expression.Lambda(eventInfo.EventHandlerType, body, sender, e);
        eventInfo.AddEventHandler(targetObject, lambda.Compile() );
    }

    public void OnEvent(object o)
    {
        Console.WriteLine(o);
    }
}

Notice that the OnEvent method is no longer static. I also assume that events you are trying to subscribe to are events that follow .NET conventions (sender + event args). This way, we can leverage contravariance and always pass a lambda of type :

(object sender, EventArgs e) => { /* */ }
Romain Verdier
Thank you, it works. And how should I modify the method in case of methodName is not the static method (i.e. it should modify some properties)?
If the method is not static, you'll need to push an instance of QueryWindow on the stack before pushing the arg0 (or arg1) and performing the call. This can be problematic since you'll certainly have to pass this instance as an argument of the dynamic method. If you post more details about this particular scenario in your question I could update my answer accordingly.
Romain Verdier
Thank you again for the help The idea is to update the RichTextBox with the data stored in EventArgs. There is the method to call (trying to get it work without adding static): public void PrintMessage(object obj) { MethodInfo methodInfo = obj.GetType().GetProperty("Message").GetGetMethod(); Paragraph paragraph = new Paragraph(); paragraph.Inlines.Add(methodInfo.Invoke(obj, null).ToString()); //MessageBox.Show(methodInfo.Invoke(obj, null).ToString()); box.Document.Blocks.Add(paragraph); }
Are you limited to .NET 2.0? Where is located the RegisterEvent method? In QueryWindow?
Romain Verdier
Yes, RegisterEvent is located in QueryWindow.And I am not limited to .NET 2.0. I can use 3.5 or 4.0 if necessary
Can you give more context about why you want to register to dynamically chosen events using dynamically chosen methods as handlers? What's your use case?Also, can we assume that all possible event handler types derive from EventHandler? If so, we can leverage contravariance and register a "universal handler".
Romain Verdier
Thank you for your patience. It is the task for the SQL Dynamite (http://sqldynamite.com/) program. The task is following: There is a SqlConnection, OracleConnection, FbConnection, MySqlConnection etc, that have InfoMessage event. It raises when the user uses "print" or "dbms_output.put_line" commands. DbConnection does not have it. So I have dynamically obtain this event and to get the Message property from (SqlInfoMessageHandlerEventArgs,OracleInfoMessageHandlerEventArgs..) and put it to WPF RichTextBox present in a form.