diff --git a/Distribution/wwDotnetBridge.PRG b/Distribution/wwDotnetBridge.PRG index 92e1af2..f551aac 100644 --- a/Distribution/wwDotnetBridge.PRG +++ b/Distribution/wwDotnetBridge.PRG @@ -26,6 +26,9 @@ SET PROCEDURE TO wwDotnetBridge ADDITIVE * wwDotnetBridge.dll #ENDIF +* Used by EventSubscription to determine which events to subscribe to. +PUBLIC wwDotnetBridgeEventHandler + ************************************************************************ * GetwwDotNetBridge @@ -67,7 +70,6 @@ FUNCTION InitializeDotnetVersion(lcVersion,llUseCom) RETURN GetwwDotnetBridge(lcVersion,llUseCom) ENDFUNC - ************************************************************* DEFINE CLASS wwDotNetBridge AS Custom ************************************************************* @@ -224,12 +226,37 @@ IF VARTYPE(this.oDotNetBridge) != "O" this.oDotNetBridge.LoadAssembly("System") this.oDotNetBridge.IsThrowOnErrorEnabled = this.lThrowOnError + this.SetSynchronizationContext() ENDIF RETURN this.oDotNetBridge ENDFUNC * CreateDotNetBridge + +************************************************************************ +* SetSynchronizationContext +**************************************** +PROTECTED FUNCTION SetSynchronizationContext() +LOCAL postMessageId +postMessageId = this.oDotNetBridge.SetSynchronizationContext(_VFP.hWnd, this) +BINDEVENT(_VFP.hWnd, postMessageId, this, "Dispatch") +ENDFUNC +* SetSynchronizationContext + + +************************************************************************ +* Dispatch +**************************************** +*** Function: Dispatches all queued send or post callbacks in the synchronization context. +*** Return: nothing +************************************************************************ +FUNCTION Dispatch(hWnd, nMsg, wParam, lParam) && Windows message handler signature +this.oDotNetBridge.Dispatch() +ENDFUNC +* Dispatch + + ************************************************************************ * SetClrVersion **************************************** @@ -481,6 +508,66 @@ ENDFUNC * InvokeMethod +************************************************************************ +* PostMethod +**************************************** +*** Function: Posts a .NET instance method to the FoxPro message queue. Useful to avoid reentrency in event handlers. +*** Return: nothing +************************************************************************ +FUNCTION PostMethod(loObject, lcMethod, lvParm1, lvParm2, lvParm3, lvParm4, lvParm5,; + lvParm6, lvParm7, lvParm8, lvParm9, lvParm10) +TRY + this.oDotNetBridge.PostInvokedMethods = .T. + this.SetError() + + LOCAL loBridge, lnParms + loBridge = this.oDotNetBridge + lnParms = PCOUNT() + DO CASE + CASE lnParms = 2 + loBridge.InvokeMethod(loObject, lcMethod) + CASE lnParms = 3 + loBridge.InvokeMethod_OneParm(loObject, lcMethod, lvParm1) + CASE lnParms = 4 + loBridge.InvokeMethod_TwoParms(loObject, lcMethod,lvParm1, lvParm2) + CASE lnParms = 5 + loBridge.InvokeMethod_ThreeParms(loObject, lcMethod,lvParm1, lvParm2, lvParm3) + CASE lnParms = 6 + loBridge.InvokeMethod_FourParms(loObject, lcMethod,lvParm1, lvParm2, lvParm3, lvParm4) + CASE lnParms = 7 + loBridge.InvokeMethod_FiveParms(loObject, lcMethod,lvParm1, lvParm2, lvParm3, lvParm4, lvParm5) + CASE lnParms = 8 + loBridge.InvokeMethod_SixParms(loObject, lcMethod,lvParm1, lvParm2, lvParm3, lvParm4, lvParm5, lvParm6) + CASE lnParms = 9 + loBridge.InvokeMethod_SevenParms(loObject, lcMethod,lvParm1, lvParm2, lvParm3, lvParm4, lvParm5, lvParm6, lvParm7) + CASE lnParms = 10 + loBridge.InvokeMethod_EightParms(loObject, lcMethod,lvParm1, lvParm2, lvParm3, lvParm4, lvParm5, lvParm6, lvParm7, lvParm8) + CASE lnParms = 11 + loBridge.InvokeMethod_NineParms(loObject, lcMethod,lvParm1, lvParm2, lvParm3, lvParm4, lvParm5, lvParm6, lvParm7, lvParm8, lvParm9) + CASE lnParms = 12 + loBridge.InvokeMethod_TenParms(loObject, lcMethod,lvParm1, lvParm2, lvParm3, lvParm4, lvParm5, lvParm6, lvParm7, lvParm8, lvParm9, lvParm10) + + OTHERWISE + LOCAL loArray as Westwind.WebConnection.ComArray + loArray = this.CreateArray("System.Object") + + LOCAL lvParm + FOR lnX = 1 TO lnParms-2 + lvParm = EVALUATE("lvParm" + TRANSFORM(lnX)) + loArray.AddItem(lvParm) + ENDFOR + ENDCASE + + IF loBridge.Error + this.SetError(loBridge.ErrorMessage) + ENDIF +FINALLY + this.oDotNetBridge.PostInvokedMethods = .F. +ENDTRY +ENDFUNC +* PostMethod + + ************************************************************************ * InvokeMethod_ParameterArray **************************************** @@ -744,7 +831,7 @@ ENDFUNC ************************************************************************ * InvokeStaticMethod **************************************** -*** Function: Calls a static .NET method with up to 5 parameters +*** Function: Calls a static .NET method with up to 10 parameters *** Assume: *** Pass: *** Return: @@ -794,6 +881,57 @@ RETURN loResult ENDFUNC * InvokeStaticMethod + +************************************************************************ +* PostStaticMethod +**************************************** +*** Function: Posts a static .NET method with up to 10 parameters to the FoxPro message queue. Useful to avoid reentrency in event handlers. +*** Return: nothing +************************************************************************ +FUNCTION PostStaticMethod(lcTypeName, lcMethod, lvParm1, lvParm2, lvParm3, lvParm4, lvParm5,; + lvParm6, lvParm7, lvParm8, lvParm9, lvParm10) +TRY + this.oDotNetBridge.PostInvokedMethods = .T. + this.SetError() + + LOCAL loBridge, lnParms + loBridge = this.oDotNetBridge + lnParms = PCOUNT() + DO CASE + CASE lnParms = 3 + loBridge.InvokeStaticMethod_OneParm(lcTypeName, lcMethod, lvParm1) + CASE lnParms = 4 + loBridge.InvokeStaticMethod_TwoParms(lcTypeName, lcMethod,lvParm1, lvParm2) + CASE lnParms = 5 + loBridge.InvokeStaticMethod_ThreeParms(lcTypeName, lcMethod,lvParm1, lvParm2, lvParm3) + CASE lnParms = 6 + loBridge.InvokeStaticMethod_FourParms(lcTypeName, lcMethod,lvParm1, lvParm2, lvParm3, lvParm4) + CASE lnParms = 7 + loBridge.InvokeStaticMethod_FiveParms(lcTypeName, lcMethod,lvParm1, lvParm2, lvParm3, lvParm4, lvParm5) + CASE lnParms = 8 + loBridge.InvokeStaticMethod_SixParms(lcTypeName, lcMethod,lvParm1, lvParm2, lvParm3, lvParm4, lvParm5,lvParm6) + CASE lnParms = 9 + loBridge.InvokeStaticMethod_SevenParms(lcTypeName, lcMethod,lvParm1, lvParm2, lvParm3, lvParm4, lvParm5,lvParm6, lvParm7) + CASE lnParms = 10 + loBridge.InvokeStaticMethod_EightParms(lcTypeName, lcMethod,lvParm1, lvParm2, lvParm3, lvParm4, lvParm5,lvParm6, lvParm7, lvParm8) + CASE lnParms = 11 + loBridge.InvokeStaticMethod_NineParms(lcTypeName, lcMethod,lvParm1, lvParm2, lvParm3, lvParm4, lvParm5,lvParm6, lvParm7, lvParm8, lvParm9) + CASE lnParms = 12 + loBridge.InvokeStaticMethod_TenParms(lcTypeName, lcMethod,lvParm1, lvParm2, lvParm3, lvParm4, lvParm5,lvParm6, lvParm7, lvParm8, lvParm9, lvParm10) + OTHERWISE + loBridge.InvokeStaticMethod(lcTypeName, lcMethod) + ENDCASE + + IF loBridge.Error + this.SetError(loBridge.ErrorMessage) + ENDIF +FINALLY + this.oDotNetBridge.PostInvokedMethods = .F. +ENDTRY +ENDFUNC +* PostStaticMethod + + ************************************************************************ * GetStaticProperty **************************************** @@ -1129,20 +1267,23 @@ ENDFUNC ************************************************************************ * SubscribeToEvents **************************************** -*** Function: Handles all events of a source object for subsequent retrieval by calling WaitForEvent. +*** Function: Handles all events of a source object. *** loSource: The object for which to subscribe to events. -*** loHandler: An object with a method OnEvent(loEventName, loParams). +*** loHandler: An object with handler methods corresponding to each event to subscribe to. *** lcPrefix: The initial part of the event handler function for each event. Defaults to "On". +*** llPost: When true, events are posted to the synchronization context. Useful for events raised on a background thread or to avoid reentrency. *** Return: A subscription object. The subscription ends when this object goes out of scope. ************************************************************************ -FUNCTION SubscribeToEvents(loSource, loHandler, lcPrefix) +FUNCTION SubscribeToEvents(loSource, loHandler, lcPrefix, llPost) IF EMPTY(lcPrefix) lcPrefix = "On" ENDIF -LOCAL loSubscription -loSubscription = CREATEOBJECT("EventSubscription") -loSubscription.Setup(this, loSource, loHandler, lcPrefix) -RETURN loSubscription + +m.wwDotnetBridgeEventHandler = loHandler +LOCAL subscription +subscription = CREATEOBJECT("EventSubscription", this, loSource, loHandler, lcPrefix, llPost) +m.wwDotnetBridgeEventHandler = null +RETURN subscription ENDFUNC * SubscribeToEvents @@ -1793,17 +1934,12 @@ DEFINE CLASS EventSubscription as AsyncCallbackEvents *: Author: Edward Brey - https://github.com/breyed *: Usage: Used internally by SubscribeToEvents ************************************************************ -HIDDEN oBridge, oHandler, oSubscriber, oPrefix - -oBridge = null -oHandler = null -oPrefix = null -oSubscriber = null +HIDDEN oSubscriber ************************************************************************ -* Setup +* Init **************************************** -*** Function: Sets up an event subscription. +*** Function: Initializes an event subscription. *** Assume: *** Pass: loBridge - dnb instance *** loSource - Source Object fires events @@ -1811,62 +1947,24 @@ oSubscriber = null *** lcPrefix - prefix for event methods *** implemented on target (defaults to "On") ************************************************************************ -FUNCTION Setup(loBridge, loSource, loHandler, lcPrefix) -this.oBridge = loBridge -this.oHandler = loHandler -this.oPrefix = lcPrefix -Private handler -handler = m.loHandler -this.oSubscriber = loBridge.CreateInstance("Westwind.WebConnection.EventSubscriber", loSource, m.lcPrefix, _Vfp) -this.HandleNextEvent() +FUNCTION Init(loBridge, loSource, loHandler, lcPrefix, llPost) +this.oSubscriber = loBridge.CreateInstance("Westwind.WebConnection.EventSubscriber", loSource, loHandler, lcPrefix, llPost, _VFP) ENDFUNC -************************************************************************ -* UnSubscribe -**************************************** -*** Function: Unsubscribes events that are currently subscribed to -************************************************************************ -FUNCTION UnSubscribe() -IF !ISNULL(THIS.oSubscriber) - this.oSubscriber.Dispose() -ENDIF -ENDFUNC - - -FUNCTION HandleNextEvent() -this.oBridge.InvokeMethodAsync(this,this.oSubscriber,"WaitForEvent") +FUNCTION Destroy() +this.Unsubscribe() ENDFUNC ************************************************************************ -* OnComplete +* Unsubscribe **************************************** -*** Function: Event Proxy that forwards the event to a function -*** named On{Event} with event's parameters. +*** Function: Unsubscribes events that are currently subscribed to ************************************************************************ -FUNCTION OnCompleted(lvResult, lcMethod) -LOCAL loParams,lParamText,lCount - -IF ISNULL(lvResult) && If the call to WaitForEvent was canceled: - RETURN -ENDIF - - -loParams=CREATEOBJECT("EMPTY") && Workaround to index into array of parameters. -lParamText = "" -IF NOT ISNULL(lvResult.Params) - lCount = 0 - FOR EACH lParam IN lvResult.Params - lCount = lCount + 1 - AddProperty(loParams,"P" + ALLTRIM(STR(lCount)),lParam) - lParamText = lParamText + ",loParams.P" + ALLTRIM(STR(lCount)) - ENDFOR -ENDIF - -IF VARTYPE(THIS.oHandler) = "O" - =EVALUATE("this.oHandler." + this.oPrefix + lvResult.Name + "("+SUBSTR(lParamText,2)+")") - this.HandleNextEvent() +FUNCTION Unsubscribe() +IF !ISNULL(this.oSubscriber) + this.oSubscriber.Dispose() + this.oSubscriber = null ENDIF - ENDFUNC ENDDEFINE @@ -2003,6 +2101,7 @@ IF VARTYPE(this.oDotNetBridge) != "O" *this.oDotNetBridge.LoadAssembly("System") this.oDotNetBridge.IsThrowOnErrorEnabled = this.lThrowOnError + this.SetSynchronizationContext() ENDIF diff --git a/DotnetBridge/Utilities/EventSubscriber.cs b/DotnetBridge/Utilities/EventSubscriber.cs index 2e134c1..f2c004b 100644 --- a/DotnetBridge/Utilities/EventSubscriber.cs +++ b/DotnetBridge/Utilities/EventSubscriber.cs @@ -1,98 +1,64 @@ -using System; -using System.Collections.Concurrent; +#nullable enable +using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Text; -using System.Threading.Tasks; +using System.Runtime.InteropServices; +using System.Threading; namespace Westwind.WebConnection -{ - /// - /// FoxPro interop access to .NET events. Handles all events of a source object for subsequent retrieval by a FoxPro client. - /// - /// For a FoxPro program to be notified of events, it should use `wwDotNetBridge.InvokeMethodAsync` to call . When asynchronously completes, the FoxPro program should handle the event it returns and then call again to wait for the next event. The FoxPro class `EventSubscription`, which is returned by `SubscribeToEvents`, encapsulates this async wait loop. - public sealed class EventSubscriber : IDisposable - { - private readonly object _source; - private readonly List _eventHandlers = new List(); - private readonly ConcurrentQueue _raisedEvents = new ConcurrentQueue(); - private TaskCompletionSource _completion = new TaskCompletionSource(); - - public EventSubscriber(object source, String prefix = "", dynamic vfp = null) - { - // Indicates that initially the client is not waiting. - _completion.SetResult(null); - - // For each event, adds a handler that calls QueueInteropEvent. - _source = source; - foreach (var ev in source.GetType().GetEvents()) - { - // handler is a PRIVATE variable defined in EventSubscription.Setup(). - Boolean hasMethod = vfp?.Eval($"PEMSTATUS(m.handler, '{prefix}{ev.Name}', 5)") ?? true; - if (!hasMethod) - continue; - - var eventParams = ev.EventHandlerType.GetMethod("Invoke").GetParameters().Select(p => Expression.Parameter(p.ParameterType)).ToArray(); - var eventHandlerLambda = Expression.Lambda(ev.EventHandlerType, - Expression.Call( - instance: Expression.Constant(this), - method: typeof(EventSubscriber).GetMethod(nameof(QueueInteropEvent), BindingFlags.NonPublic | BindingFlags.Instance), - arg0: Expression.Constant(ev.Name), - arg1: Expression.NewArrayInit(typeof(object), eventParams.Select(p => Expression.Convert(p, typeof(object))))), - eventParams); - var eventHandler = eventHandlerLambda.Compile(); - ev.AddEventHandler(source, eventHandler); - _eventHandlers.Add(new DelegateInfo(eventHandler, ev)); - } - } - - class DelegateInfo - { - public DelegateInfo(Delegate handler, EventInfo eventInfo) +{ + /// + /// Subscribes to all events for which a handler object has corresponding methods. + /// + public sealed class EventSubscriber : IDisposable + { + private readonly object _eventSource; + private readonly object _handler; + private readonly SynchronizationContext? _synchronizationContext; + private readonly List<(EventInfo, Delegate)> _eventDelegates = []; + + private static readonly MethodInfo invokeMethod = typeof(EventSubscriber).GetMethod(nameof(InvokeMethod), BindingFlags.NonPublic | BindingFlags.Instance); + + public EventSubscriber(object eventSource, object handler, string prefix, bool post, dynamic vfp) + { + _eventSource = eventSource; + _handler = handler; + _synchronizationContext = post ? SynchronizationContext.Current : null; + var instanceExpression = Expression.Constant(this); + var handlerExpression = Expression.Constant(handler); + + foreach (var eventInfo in eventSource.GetType().GetEvents()) { - Delegate = handler; - EventInfo = eventInfo; - } - - public Delegate Delegate { get; } - public EventInfo EventInfo { get; } - } - - public void Dispose() - { - foreach (var item in _eventHandlers) - item.EventInfo.RemoveEventHandler(_source, item.Delegate); - _completion.TrySetCanceled(); - } - - private void QueueInteropEvent(string name, object[] parameters) - { - var interopEvent = new RaisedEvent { Name = name, Params = parameters }; - if (!_completion.TrySetResult(interopEvent)) - _raisedEvents.Enqueue(interopEvent); - } - - /// - /// Waits until an event is raised, or returns immediately if a queued event is available. - /// - /// The next event, or null if this subscriber has been disposed. - public RaisedEvent WaitForEvent() - { - if (_raisedEvents.TryDequeue(out var interopEvent)) return interopEvent; - _completion = new TaskCompletionSource(); - var task = _completion.Task; - - task.Wait(); - - return task.IsCanceled ? null : task.Result; - } - } - - public class RaisedEvent - { - public string Name { get; internal set; } - public object[] Params { get; internal set; } + string methodName = prefix + eventInfo.Name; + bool hasMethod = vfp.Eval($"PEMSTATUS(m.wwDotnetBridgeEventHandler, '{methodName}', 5)"); + if (!hasMethod) + continue; + + var eventHandlerType = eventInfo.EventHandlerType; + var paramExpressions = eventHandlerType.GetMethod("Invoke").GetParameters().Select(p => Expression.Parameter(p.ParameterType, p.Name)).ToArray(); + var arguments = paramExpressions.Select(p => Expression.Convert(p, typeof(object))).ToArray(); + var callExpression = Expression.Call(instanceExpression, invokeMethod, handlerExpression, Expression.Constant(methodName), Expression.NewArrayInit(typeof(object), arguments)); + var eventDelegate = Expression.Lambda(eventHandlerType, callExpression, paramExpressions).Compile(); + eventInfo.AddEventHandler(eventSource, eventDelegate); + _eventDelegates.Add((eventInfo, eventDelegate)); + } + } + + public void Dispose() + { + foreach ((var eventInfo, var eventDelegate) in _eventDelegates) + eventInfo.RemoveEventHandler(_eventSource, eventDelegate); + Marshal.FinalReleaseComObject(_handler); + } + + private void InvokeMethod(object handler, string methodName, object[] arguments) + { + if (_synchronizationContext != null) + _synchronizationContext.Post(_ => handler.GetType().InvokeMember(methodName, BindingFlags.InvokeMethod, null, handler, arguments), null); + else + handler.GetType().InvokeMember(methodName, BindingFlags.InvokeMethod, null, handler, arguments); + } } } diff --git a/DotnetBridge/Utilities/FoxProSynchronizationContext.cs b/DotnetBridge/Utilities/FoxProSynchronizationContext.cs new file mode 100644 index 0000000..f225633 --- /dev/null +++ b/DotnetBridge/Utilities/FoxProSynchronizationContext.cs @@ -0,0 +1,50 @@ +#nullable enable +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Westwind.WebConnection +{ + /// + /// Synchronizes tasks with the FoxPro main thread. + /// + /// When one or more tasks are ready to run, posts a Windows message to the main FoxPro window. + sealed class FoxProSynchronizationContext(int hwnd, object taskWaiter) : SynchronizationContext + { + private readonly IntPtr _hwnd = (IntPtr)hwnd; + private readonly ConcurrentQueue<(SendOrPostCallback handler, object? state)> _postQueue = []; + private readonly dynamic _taskWaiter = taskWaiter; + + /// + /// Gets the ID of the Windows message posted when a task is ready to run. + /// + public int PostMessageId { get; private set; } = RegisterWindowMessage("FoxProSynchronizationContextDispatch"); + + public override void Post(SendOrPostCallback d, object? state) + { + _postQueue.Enqueue((d, state)); + PostMessage(_hwnd, PostMessageId, IntPtr.Zero, IntPtr.Zero); + } + + /// + /// Dispatches all queued send or post callbacks in the synchronization context. Called when a Windows message with ID is received. + /// + public void Dispatch() + { + while (_postQueue.TryDequeue(out var postedEvent)) + try { postedEvent.handler(postedEvent.state); } catch { } + } + + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [DllImport("user32.dll", CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true)] + private static extern int RegisterWindowMessage(string lpString); + + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [DllImport("user32.dll", SetLastError = true)] + private static extern bool PostMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); + } +} diff --git a/DotnetBridge/wwDotNetBridge.cs b/DotnetBridge/wwDotNetBridge.cs index f89a2b2..5bedf6e 100644 --- a/DotnetBridge/wwDotNetBridge.cs +++ b/DotnetBridge/wwDotNetBridge.cs @@ -34,20 +34,21 @@ // comment for OpenSource version // #define WestwindProduct +using Microsoft.Win32; using System; using System.Collections; using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Reflection; -using System.IO; using System.Data; +using System.IO; using System.Net; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; +using System.Threading; using System.Threading.Tasks; -using Microsoft.Win32; -using System.Runtime.CompilerServices; - - + + #if WestwindProduct using Newtonsoft.Json; #endif @@ -73,6 +74,13 @@ public class wwDotNetBridge private static bool _firstLoad = true; + private FoxProSynchronizationContext _synchronizationContext; + + /// + /// Gets or sets whether invoked methods should be posted to the message queue instead of called immediately. + /// + public bool PostInvokedMethods { get; set; } + /// /// Returns error information if the call fails /// @@ -119,6 +127,18 @@ private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs a return ass; } + /// + /// Sets the current synchronization content to use the FoxPro main thread. + /// + /// The ID of the Windows message posted when a task is ready to run. + public int SetSynchronizationContext(int hwnd, object taskWaiter) + { + _synchronizationContext = new FoxProSynchronizationContext(hwnd, taskWaiter); + SynchronizationContext.SetSynchronizationContext(_synchronizationContext); + return _synchronizationContext.PostMessageId; + } + + public void Dispatch() => _synchronizationContext.Dispatch(); #region LoadAssembly Routines @@ -696,14 +716,17 @@ public object InvokeStaticMethod_TenParms(string TypeName, string Method, object Parm9, Parm10); } - /// - /// Invokes a static method - /// - /// - /// - /// - /// - internal object InvokeStaticMethod_Internal(string TypeName, string Method, params object[] args) + internal object InvokeStaticMethod_Internal(string TypeName, string method, params object[] args) + { + if (PostInvokedMethods) + { + _synchronizationContext.Post(_ => InvokeStaticMethod_Now(TypeName, method, args), null); + return null; + } + return InvokeStaticMethod_Now(TypeName, method, args); + } + + internal object InvokeStaticMethod_Now(string TypeName, string Method, params object[] args) { SetError(); @@ -962,7 +985,22 @@ public object InvokeMethod_ParameterArray(object instance, string method, object return InvokeMethod_InternalWithObjectArray(instance, method, ParmArray.Instance as object[]); } - internal object InvokeMethod_Internal(object instance, string method, params object[] args) + internal object InvokeMethod_Internal(object instance, string method, params object[] args) + { + if (PostInvokedMethods) + { + PostMethod_Internal(instance, method, args); + return null; + } + return InvokeMethod_Now(instance, method, args); + } + + internal void PostMethod_Internal(object instance, string method, params object[] args) + { + _synchronizationContext.Post(_ => InvokeMethod_Now(instance, method, args), null); + } + + internal object InvokeMethod_Now(object instance, string method, params object[] args) { var fixedInstance = FixupParameter(instance); @@ -1441,7 +1479,7 @@ public void InvokeTaskMethodAsync( ex = WrapException(LastException); InvokeMethod_Internal(callBack, "onError", ex.Message, ex, method); } - }); + }, TaskScheduler.FromCurrentSynchronizationContext()); } catch (Exception ex) { @@ -1519,7 +1557,7 @@ private void _InvokeMethodAsync(object parmList) { try { - InvokeMethod_Internal(callBack, "onError", ex.Message, ex.GetBaseException(), method); + PostMethod_Internal(callBack, "onError", ex.Message, ex.GetBaseException(), method); } catch { @@ -1536,7 +1574,7 @@ private void _InvokeMethodAsync(object parmList) { try { - InvokeMethod_Internal(callBack, "onCompleted", result, method); + PostMethod_Internal(callBack, "onCompleted", result, method); } catch (Exception ex) { diff --git a/DotnetBridge/wwDotNetBridge.csproj b/DotnetBridge/wwDotNetBridge.csproj index 73575e1..6b51fdc 100644 --- a/DotnetBridge/wwDotNetBridge.csproj +++ b/DotnetBridge/wwDotNetBridge.csproj @@ -23,6 +23,7 @@ https://github.com/RickStrahl/wwDotnetBridge git true + latest $(NoWarn);CS1591;CS1572;CS1573