using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Runtime.Remoting.Proxies; using System.Runtime.Remoting.Messaging; using System.Reflection.Emit; using System.Reflection; namespace AsyncFuncTesting { public class CallbackWaiter { private ManualResetEvent waitHandle = new ManualResetEvent(false); private Queue events; public CallbackWaiter() { events = new Queue(); } // generate a function of type T, which records the event into the queue public T MakeHandler(string name) { Type targetType = typeof(T); if (!targetType.IsSubclassOf(typeof(System.Delegate))) { throw new ArgumentException("Type parameter T should be a delegate type."); } MethodInfo targetMethodInfo = targetType.GetMethod("Invoke"); Type returnType = targetMethodInfo.ReturnType; if (returnType != typeof(void)) { // todo: can we support other return types too? throw new ArgumentException("Type parameter T should be a delegate with void return."); } Type[] argTypes = GetTargetParameterTypes(targetMethodInfo); DynamicMethod newMethod = new DynamicMethod("", typeof(void), argTypes, typeof(CallbackWaiter)); GenerateBody(newMethod, name, argTypes); return (T)(object) newMethod.CreateDelegate(targetType, this); } private static void GenerateBody(DynamicMethod newMethod, string name, Type[] argTypes) { ILGenerator body = newMethod.GetILGenerator(); // create new ArrayList body.DeclareLocal(typeof(System.Collections.ArrayList)); body.Emit(OpCodes.Newobj, GetArrayListCtorInfo()); body.Emit(OpCodes.Stloc_0); // add a true, as a placeholder for timeout status // todo: add a true instead body.Emit(OpCodes.Ldloc_0); body.Emit(OpCodes.Ldnull); body.EmitCall(OpCodes.Call, GetArrayListAddMethodInfo(), null); body.Emit(OpCodes.Pop); // add the event name to the ArrayList body.Emit(OpCodes.Ldloc_0); body.Emit(OpCodes.Ldstr, name); body.EmitCall(OpCodes.Call, GetArrayListAddMethodInfo(), null); body.Emit(OpCodes.Pop); // add each argument to the ArrayList, except the first one ("this") for (int indexArg = 1; indexArg < argTypes.Length; indexArg++) { body.Emit(OpCodes.Ldloc_0); body.Emit(OpCodes.Ldarg, indexArg); if (argTypes[indexArg].IsValueType) { body.Emit(OpCodes.Box, argTypes[indexArg]); } body.EmitCall(OpCodes.Call, GetArrayListAddMethodInfo(), null); body.Emit(OpCodes.Pop); } body.Emit(OpCodes.Ldarg_0); body.Emit(OpCodes.Ldloc_0); body.Emit(OpCodes.Call, GetRecordMethodInfo()); body.Emit(OpCodes.Ret); } private static Type[] GetTargetParameterTypes(MethodInfo targetMethodInfo) { ParameterInfo[] targetParameters = targetMethodInfo.GetParameters(); Type[] argTypes = new Type[targetParameters.Length + 1]; // first argument is "this", which is an CallbackWaiter argTypes[0] = typeof(CallbackWaiter); // the following arguments match the arguments of the target delegate for (int i = 0; i < targetParameters.Length; i++) { argTypes[i + 1] = targetParameters[i].ParameterType; } return argTypes; } private static MethodInfo GetRecordMethodInfo() { return typeof(CallbackWaiter).GetMethod("Record", BindingFlags.Instance | BindingFlags.NonPublic); } private static ConstructorInfo GetArrayListCtorInfo() { return typeof(System.Collections.ArrayList).GetConstructor(new Type[0] { }); } private static MethodInfo GetArrayListAddMethodInfo() { return typeof(System.Collections.ArrayList).GetMethod("Add", new Type[] { typeof(object) }); } // returns true if a callback was called in time // returns false otherwise public EventMessage Wait(int timeout) { lock (events) { if (events.Count > 0) { return events.Dequeue(); } waitHandle.Reset(); } bool eventOccured = waitHandle.WaitOne(timeout, false); lock (events) { if (eventOccured) { return events.Dequeue(); } else { return new EventMessage(); } } } private void Record(System.Collections.IList rawEvent) { if (rawEvent.Count < 2) { throw new Exception("Something is corrupted"); } rawEvent[0] = true; // this is an event, not a timeout lock (events) { events.Enqueue(new EventMessage(rawEvent)); } waitHandle.Set(); } } public class EventMessage { // The convention is that the List contains: // - timeout status // - name of the event (not applicable if timeout) // - list of arguments (not applicable if timeout) private System.Collections.IList rawEvent; /// /// Creates an EventMessage representing an actual event /// /// public EventMessage(System.Collections.IList rawEvent) { this.rawEvent = rawEvent; } /// /// Creates an EventMessage representing a timeout /// public EventMessage() { System.Collections.ArrayList timeoutEvent = new System.Collections.ArrayList(); timeoutEvent.Add(false); this.rawEvent = timeoutEvent; } public bool IsEvent() { return (bool)rawEvent[0]; } public bool IsTimeout() { return !IsEvent(); } private void NoTimeoutAllowed() { if (IsTimeout()) { throw new Exception("Timeout events do not have any additional information."); } } public string EventName { get { NoTimeoutAllowed(); return (string)rawEvent[1]; } } public object this[int index] { get { NoTimeoutAllowed(); return rawEvent[index + 2]; // skip over the timeout status and event name } } public int Length { get { NoTimeoutAllowed(); return rawEvent.Count - 2; } } } } /* .method public hidebysig instance void ExampleMethod(class AsyncFuncTesting.ICallbackRecorder a) cil managed { // Code size 11 (0xb) .maxstack 8 IL_0000: nop IL_0001: ldarg.1 IL_0002: callvirt instance void AsyncFuncTesting.ICallbackRecorder::Record() IL_0007: nop IL_0008: br.s IL_000a IL_000a: ret } // end of method SynchronousCallback::ExampleMethod */ /* public void ExampleMethod(ICallbackRecorder a) { a.Record(); return; } */ /* .method public hidebysig instance void ExampleMethod(object b, int32 c, int32 d) cil managed { // Code size 50 (0x32) .maxstack 2 .locals init ([0] class [mscorlib]System.Collections.ArrayList args) IL_0000: nop IL_0001: newobj instance void [mscorlib]System.Collections.ArrayList::.ctor() IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: ldarg.1 IL_0009: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object) IL_000e: pop IL_000f: ldloc.0 IL_0010: ldarg.2 IL_0011: box [mscorlib]System.Int32 IL_0016: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object) IL_001b: pop IL_001c: ldloc.0 IL_001d: ldarg.3 IL_001e: box [mscorlib]System.Int32 IL_0023: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object) IL_0028: pop IL_0029: ldarg.0 IL_002a: ldloc.0 IL_002b: call instance void AsyncFuncTesting.CallbackWaiter::Record(class [mscorlib]System.Collections.IList) IL_0030: nop IL_0031: ret } // end of method CallbackWaiter::ExampleMethod */ /* public void ExampleMethod(object b, int c, Int32 d) { System.Collections.ArrayList args = new System.Collections.ArrayList(); args.Add(b); args.Add(c); args.Add(d); Record(args); } */