// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; using System.Collections.Generic; using System.Reflection; using System.Runtime.InteropServices; using System.Threading; using System.Windows.Forms; namespace Debugger { public delegate T MethodInvokerWithReturnValue(); public enum CallMethod {DirectCall, Manual, HiddenForm, HiddenFormWithTimeout}; public class MTA2STA { Form hiddenForm; IntPtr hiddenFormHandle; System.Threading.Thread targetThread; CallMethod callMethod = CallMethod.HiddenFormWithTimeout; Queue pendingCalls = new Queue(); ManualResetEvent pendingCallsNotEmpty = new ManualResetEvent(false); WaitHandle EnqueueCall(MethodInvoker callDelegate) { lock (pendingCalls) { ManualResetEvent callDone = new ManualResetEvent(false); pendingCalls.Enqueue(delegate{ callDelegate(); callDone.Set(); }); pendingCallsNotEmpty.Set(); return callDone; } } /// /// Wait until a call a made /// public void WaitForCall() { pendingCallsNotEmpty.WaitOne(); } public void WaitForCall(TimeSpan timeout) { pendingCallsNotEmpty.WaitOne(timeout, false); } /// /// Performs all waiting calls on the current thread /// public void PerformAllCalls() { while (true) { if (!PerformCall()) { return; } } } /// /// Performs all waiting calls on the current thread /// /// /// Private - user should always drain the queue - otherwise Prcoess.RaisePausedEvents might fail /// bool PerformCall() { MethodInvoker nextMethod; lock (pendingCalls) { if (pendingCalls.Count > 0) { nextMethod = pendingCalls.Dequeue(); } else { pendingCallsNotEmpty.Reset(); return false; } } nextMethod(); return true; } public CallMethod CallMethod { get { return callMethod; } set { callMethod = value; } } public MTA2STA() { targetThread = System.Threading.Thread.CurrentThread; hiddenForm = new Form(); // Force handle creation hiddenFormHandle = hiddenForm.Handle; } /// /// SoftWait waits for any of the given WaitHandles and allows processing of calls during the wait /// public int SoftWait(params WaitHandle[] waitFor) { List waits = new List (waitFor); waits.Add(pendingCallsNotEmpty); while(true) { int i = WaitHandle.WaitAny(waits.ToArray()); PerformAllCalls(); if (i < waits.Count - 1) { // If not pendingCallsNotEmpty return i; } } } /// /// Schedules invocation of method and returns immediately /// public WaitHandle AsyncCall(MethodInvoker callDelegate) { WaitHandle callDone = EnqueueCall(callDelegate); TriggerInvoke(); return callDone; } public T Call(MethodInvokerWithReturnValue callDelegate) { T returnValue = default(T); Call(delegate { returnValue = callDelegate(); }, true); return returnValue; } public void Call(MethodInvoker callDelegate) { Call(callDelegate, false); } void Call(MethodInvoker callDelegate, bool hasReturnValue) { // Enqueue the call WaitHandle callDone = EnqueueCall(callDelegate); if (targetThread == System.Threading.Thread.CurrentThread) { PerformAllCalls(); return; } // We have the call waiting in queue, we need to call it (not waiting for it to finish) TriggerInvoke(); // Wait for the call to finish if (!hasReturnValue && callMethod == CallMethod.HiddenFormWithTimeout) { // Give it 5 seconds to run if (!callDone.WaitOne(5000, true)) { System.Console.WriteLine("Call time out! (continuing)"); System.Console.WriteLine(new System.Diagnostics.StackTrace(true).ToString()); } } else { callDone.WaitOne(); } } void TriggerInvoke() { switch (callMethod) { case CallMethod.DirectCall: PerformAllCalls(); break; case CallMethod.Manual: // Nothing we can do - someone else must call SoftWait or Pulse break; case CallMethod.HiddenForm: case CallMethod.HiddenFormWithTimeout: hiddenForm.BeginInvoke((MethodInvoker)PerformAllCalls); break; } } public static object MarshalParamTo(object param, Type outputType) { if (param is IntPtr) { return MarshalIntPtrTo((IntPtr)param, outputType); } else { return param; } } public static T MarshalIntPtrTo(IntPtr param) { return (T)MarshalIntPtrTo(param, typeof(T)); } public static object MarshalIntPtrTo(IntPtr param, Type outputType) { // IntPtr requested as output (must be before the null check so that we pass IntPtr.Zero) if (outputType == typeof(IntPtr)) { return param; } // The parameter is null pointer if ((IntPtr)param == IntPtr.Zero) { return null; } // String requested as output if (outputType == typeof(string)) { return Marshal.PtrToStringAuto((IntPtr)param); } // Marshal a COM object object comObject = Marshal.GetObjectForIUnknown(param); Debugger.Interop.TrackedComObjects.Track(comObject); return comObject; } /// /// Uses reflection to call method. Automaticaly marshals parameters. /// /// Targed object which contains the method. In case of static mehod pass the Type /// The name of the function to call /// Parameters which should be send to the function. Parameters will be marshaled to proper type. /// Return value of the called function public static object InvokeMethod(object targetObject, string functionName, object[] functionParameters) { System.Reflection.MethodInfo method; if (targetObject is Type) { method = ((Type)targetObject).GetMethod(functionName); } else { method = targetObject.GetType().GetMethod(functionName); } ParameterInfo[] methodParamsInfo = method.GetParameters(); object[] convertedParams = new object[methodParamsInfo.Length]; for (int i = 0; i < convertedParams.Length; i++) { convertedParams[i] = MarshalParamTo(functionParameters[i], methodParamsInfo[i].ParameterType); } try { if (targetObject is Type) { return method.Invoke(null, convertedParams); } else { return method.Invoke(targetObject, convertedParams); } } catch (System.Exception exception) { throw new Debugger.DebuggerException("Invoke of " + functionName + " failed.", exception); } } } }