Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Emit event at runtime

I wish to generate (emit) class that implement an Interface at run-time with C#.

I have succeeded to emit methods and properties, but I failed to emit an event.

Here is the code I wish to emit:

Namespace: TestInterfaces, Class: TestIImplementMe.

public event EventHandler SimpleEvent;

Here is the IL code generated:

.event [mscorlib]System.EventHandler SimpleEvent
{
    .addon instance void TestInterfaces.TestIImplementMe::add_SimpleEvent(class [mscorlib]System.EventHandler)
    .removeon instance void TestInterfaces.TestIImplementMe::remove_SimpleEvent(class [mscorlib]System.EventHandler)
}

.method public final hidebysig specialname newslot virtual 
    instance void add_SimpleEvent (
        class [mscorlib]System.EventHandler 'value'
    ) cil managed 
{
    // Method begins at RVA 0x330c
    // Code size 48 (0x30)
    .maxstack 3
    .locals init (
        [0] class [mscorlib]System.EventHandler,
        [1] class [mscorlib]System.EventHandler,
        [2] class [mscorlib]System.EventHandler,
        [3] bool
    )

    IL_0000: ldarg.0
    IL_0001: ldfld class [mscorlib]System.EventHandler TestInterfaces.TestIImplementMe::SimpleEvent
    IL_0006: stloc.0
    // loop start (head: IL_0007)
        IL_0007: ldloc.0
        IL_0008: stloc.1
        IL_0009: ldloc.1
        IL_000a: ldarg.1
        IL_000b: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
        IL_0010: castclass [mscorlib]System.EventHandler
        IL_0015: stloc.2
        IL_0016: ldarg.0
        IL_0017: ldflda class [mscorlib]System.EventHandler TestInterfaces.TestIImplementMe::SimpleEvent
        IL_001c: ldloc.2
        IL_001d: ldloc.1
        IL_001e: call !!0 [mscorlib]System.Threading.Interlocked::CompareExchange<class [mscorlib]System.EventHandler>(!!0&, !!0, !!0)
        IL_0023: stloc.0
        IL_0024: ldloc.0
        IL_0025: ldloc.1
        IL_0026: ceq
        IL_0028: ldc.i4.0
        IL_0029: ceq
        IL_002b: stloc.3
        IL_002c: ldloc.3
        IL_002d: brtrue.s IL_0007
    // end loop
    IL_002f: ret
} // end of method TestIImplementMe::add_SimpleEvent

.method public final hidebysig specialname newslot virtual 
    instance void remove_SimpleEvent (
        class [mscorlib]System.EventHandler 'value'
    ) cil managed 
{
    // Method begins at RVA 0x3348
    // Code size 48 (0x30)
    .maxstack 3
    .locals init (
        [0] class [mscorlib]System.EventHandler,
        [1] class [mscorlib]System.EventHandler,
        [2] class [mscorlib]System.EventHandler,
        [3] bool
    )

    IL_0000: ldarg.0
    IL_0001: ldfld class [mscorlib]System.EventHandler TestInterfaces.TestIImplementMe::SimpleEvent
    IL_0006: stloc.0
    // loop start (head: IL_0007)
        IL_0007: ldloc.0
        IL_0008: stloc.1
        IL_0009: ldloc.1
        IL_000a: ldarg.1
        IL_000b: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Remove(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
        IL_0010: castclass [mscorlib]System.EventHandler
        IL_0015: stloc.2
        IL_0016: ldarg.0
        IL_0017: ldflda class [mscorlib]System.EventHandler TestInterfaces.TestIImplementMe::SimpleEvent
        IL_001c: ldloc.2
        IL_001d: ldloc.1
        IL_001e: call !!0 [mscorlib]System.Threading.Interlocked::CompareExchange<class [mscorlib]System.EventHandler>(!!0&, !!0, !!0)
        IL_0023: stloc.0
        IL_0024: ldloc.0
        IL_0025: ldloc.1
        IL_0026: ceq
        IL_0028: ldc.i4.0
        IL_0029: ceq
        IL_002b: stloc.3
        IL_002c: ldloc.3
        IL_002d: brtrue.s IL_0007
    // end loop
    IL_002f: ret
} // end of method TestIImplementMe::remove_SimpleEvent

I managed to do all but this line: IL_001e: call !!0 [mscorlib]System.Threading.Interlocked::CompareExchange(!!0&, !!0, !!0)

What is the meaning of: !!0, !!0&?

How do I call this method with IL emit?

I tried to call the method, but when I add handler:

obj.SimpleEvent += new EventHandler(obj_SimpleEvent);

I get this exception:

System.BadImageFormatException was unhandled
  Message=An attempt was made to load a program with an incorrect format. (Exception from HRESULT: 0x8007000B)
  Source=DataBuilderAssembly
  StackTrace:
       at NewTest.add_SimpleEvent(EventHandler value)
       at TestInterfaces.Program.Main(String[] args) in D:\Dev\Private\TestInterfaces\TestInterfaces\Program.cs:line 47
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()

Here is the code to emit event's add method where

methodName = "add_SimpleEvent";
actionHanler = typeof(Delegate).GetMethod("Combine", new Type[]{typeof(Delegate),typeof(Delegate)});
eventField = typeBuilder.DefineField(info.Name, info.EventHandlerType, FieldAttributes.Private);
compareExchange = GetGenericMethod(typeof(Interlocked), "CompareExchange");// finds the correct method to call.
// thanks to @marc, create the specific method with the correct type
compareExchange = compareExchange.MakeGenericMethod(info.EventHandlerType);

GenerateEventMethod

private static MethodBuilder GenerateEventMethod(string methodName, MethodInfo actionHanler, TypeBuilder typeBuilder, EventInfo info, FieldBuilder eventField, MethodInfo compareExchange)
{
    MethodBuilder method = typeBuilder.DefineMethod(methodName, InternalMethodsAttributes, null, new Type[] { info.EventHandlerType });
    method.DefineParameter(0, ParameterAttributes.Retval, null);
    method.DefineParameter(1, ParameterAttributes.In, "value");
    ILGenerator il = method.GetILGenerator();
    il.DeclareLocal(info.EventHandlerType);
    il.DeclareLocal(info.EventHandlerType);
    il.DeclareLocal(info.EventHandlerType);
    il.DeclareLocal(typeof (bool));

    Label loop = il.DefineLabel();

    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldfld, eventField);
    il.Emit(OpCodes.Stloc_0);

    il.MarkLabel(loop);// loop start (head: IL_0007)

    il.Emit(OpCodes.Ldloc_0);
    il.Emit(OpCodes.Stloc_1);
    il.Emit(OpCodes.Ldloc_1);
    il.Emit(OpCodes.Ldarg_1);

    il.Emit(OpCodes.Call, actionHanler);
    il.Emit(OpCodes.Castclass, info.EventHandlerType);

    il.Emit(OpCodes.Stloc_2);
    il.Emit(OpCodes.Ldarg_0);

    il.Emit(OpCodes.Ldflda, eventField);

    il.Emit(OpCodes.Ldloc_2);
    il.Emit(OpCodes.Ldloc_1);

    // How to do this?
    //IL_001e: call !!0 [mscorlib]System.Threading.Interlocked::CompareExchange<class [mscorlib]System.EventHandler`1<class [mscorlib]System.ConsoleCancelEventArgs>>(!!0&, !!0, !!0)
    //il.Emit(OpCodes.Call, !!0 compareExchange(!!0&,!!0, !!0));
    il.Emit(OpCodes.Call, compareExchange);

    il.Emit(OpCodes.Stloc_0);
    il.Emit(OpCodes.Ldloc_0);
    il.Emit(OpCodes.Ldloc_1);
    il.Emit(OpCodes.Ceq);
    il.Emit(OpCodes.Ldc_I4_0);
    il.Emit(OpCodes.Ceq);
    il.Emit(OpCodes.Stloc_3);
    il.Emit(OpCodes.Ldloc_3);
    il.Emit(OpCodes.Brtrue_S, loop);
    // end loop
    il.Emit(OpCodes.Ret);

    return method;
}

GetGenericMethod (specific for this issue):

private static MethodInfo GetGenericMethod(Type type, string methodName)
{
    var q = from m in type.GetMethods()
            where m.Name == methodName && m.IsGenericMethod
            select m;
    return q.FirstOrDefault();
}

Thanks, Ofir

like image 719
Ofir Avatar asked Sep 02 '12 08:09

Ofir


2 Answers

!!0 is the T in CompareExchange<T> (i.e. !!0 is the first (zero-based) generic type parameter), and !!0& is ref T - but all of this is just specifying the overload. The important bit is the ldflda (loads by-ref the field), ldloc.2 and ldloc.1 in the lines before that, and the fact that it returns a T.

like image 170
Marc Gravell Avatar answered Sep 22 '22 09:09

Marc Gravell


I would suggest to write the code in C#, compile it, and then use one of your favorite .NET decompilers to check what is the exact IL code. After doing this, it should be pretty easy to generate the same code dynamically.

like image 38
Maciek Talaska Avatar answered Sep 19 '22 09:09

Maciek Talaska