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