diff --git a/Distribution/wwDotnetBridge.PRG b/Distribution/wwDotnetBridge.PRG
index af61a87..1663a5a 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,23 @@ 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()
+this.oDotNetBridge.SetSynchronizationContext(_VFP.hWnd)
+ENDFUNC
+* SetSynchronizationContext
+
+
************************************************************************
* SetClrVersion
****************************************
@@ -481,6 +494,66 @@ ENDFUNC
* InvokeMethod
+************************************************************************
+* PostMethod
+****************************************
+*** Function: Posts a .NET instance method to the FoxPro message queue. Useful to avoid reentrancy 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
****************************************
@@ -769,7 +842,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:
@@ -819,6 +892,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 reentrancy 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
****************************************
@@ -1154,20 +1278,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".
-*** Return: A subscription object. The subscription ends when this object goes out of scope.
+*** llPost: Events are posted to the synchronization context if on a background thread or if llPost is true. It is useful to set llPost to true if your event handlers call back into an event source that does not support reentrancy.
+*** Return: A subscription object. The subscription ends when it is no longer reference or when Unsubscribe() is called.
************************************************************************
-FUNCTION SubscribeToEvents(loSource, loHandler, lcPrefix)
+FUNCTION SubscribeToEvents(loSource, loHandler, lcPrefix, llPost)
IF VARTYPE(lcPrefix) # "C"
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
@@ -1824,17 +1951,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
@@ -1842,62 +1964,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
@@ -2034,6 +2118,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..53d65b2 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 bool _post;
+ 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;
+ _post = post;
+ 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 (_post || Thread.CurrentThread != wwDotNetBridge._mainThread)
+ wwDotNetBridge._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..d2cfc5b
--- /dev/null
+++ b/DotnetBridge/Utilities/FoxProSynchronizationContext.cs
@@ -0,0 +1,103 @@
+#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.
+ /// Subclasses the window to receive the posted message and dispatch the tasks.
+ /// Unlike FoxPro BINDEVENT, subclassing processes messages even when FoxPro pumps messages from a dispatched task (e.g. from a modal form).
+ ///
+ internal sealed class FoxProSynchronizationContext : SynchronizationContext
+ {
+ private readonly IntPtr _hwnd;
+ private readonly wwDotNetBridge _bridge;
+ private readonly ConcurrentQueue<(SendOrPostCallback handler, object? state)> _postQueue = [];
+ private readonly WndProcDelegate _wndProcDelegate;
+ private readonly IntPtr _originalWndProc;
+ private readonly uint _postMessageId;
+
+ public FoxProSynchronizationContext(int hwnd, wwDotNetBridge bridge)
+ {
+ _hwnd = (IntPtr)hwnd;
+ _bridge = bridge;
+ _wndProcDelegate = WndProc; // Prevents the delegate from being garbage collected.
+ _originalWndProc = SetWindowLongPtr(_hwnd, GWLP_WNDPROC, Marshal.GetFunctionPointerForDelegate(_wndProcDelegate));
+ _postMessageId = RegisterWindowMessage("FoxProSynchronizationContextDispatch");
+ }
+
+ private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
+ {
+ if (msg == _postMessageId)
+ {
+ Dispatch();
+ return IntPtr.Zero;
+ }
+
+ return CallWindowProc(_originalWndProc, hWnd, msg, wParam, lParam);
+ }
+
+ ///
+ /// Posts a message to indicate that there are posts ready to dispatch. Thread safe.
+ ///
+ public override void Post(SendOrPostCallback d, object? state)
+ {
+ _postQueue.Enqueue((d, state));
+
+ if (!PostMessage(_hwnd, _postMessageId, IntPtr.Zero, IntPtr.Zero))
+ _bridge.LastException = new OutOfMemoryException("Failed to post dispatch message.");
+ }
+
+ ///
+ /// Dispatches all queued send or post callbacks in the synchronization context.
+ ///
+ private void Dispatch()
+ {
+ while (_postQueue.TryDequeue(out var post))
+ {
+ try
+ {
+ post.handler(post.state);
+ }
+ catch (Exception ex)
+ {
+ _bridge.LastException = ex;
+ }
+ }
+ }
+
+ ///
+ /// Starts a dispatch operation. Used by external code to dispatch queued callbacks.
+ ///
+ public override void OperationStarted() => Dispatch();
+
+ private const int GWLP_WNDPROC = -4;
+
+ private delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
+
+ [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
+ [DllImport("user32.dll", EntryPoint = "SetWindowLongA")]
+ private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
+
+ [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
+ [DllImport("user32.dll")]
+ private static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
+
+ [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
+ [DllImport("user32.dll", CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true)]
+ private static extern uint RegisterWindowMessage(string lpString);
+
+ [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
+ [DllImport("user32.dll", SetLastError = true)]
+ private static extern bool PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
+ }
+}
diff --git a/DotnetBridge/wwDotNetBridge.cs b/DotnetBridge/wwDotNetBridge.cs
index c4131a7..de029d2 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
@@ -71,7 +72,14 @@ namespace Westwind.WebConnection
public class wwDotNetBridge
{
- private static bool _firstLoad = true;
+ internal static Thread _mainThread;
+
+ internal static 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
@@ -87,12 +95,14 @@ public class wwDotNetBridge
public wwDotNetBridge()
{
-
+ bool firstLoad = _mainThread == null;
+ _mainThread ??= Thread.CurrentThread;
+
if (Environment.Version.Major >= 4)
{
LoadAssembly("System.Core");
- if (_firstLoad)
+ if (firstLoad)
{
if (!ServicePointManager.SecurityProtocol.HasFlag(SecurityProtocolType.Tls12))
{
@@ -103,9 +113,6 @@ public wwDotNetBridge()
SecurityProtocolType.Tls;
}
- _firstLoad = false;
-
-
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
}
@@ -119,6 +126,14 @@ private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs a
return ass;
}
+ ///
+ /// Sets the current synchronization context to use the FoxPro main thread.
+ ///
+ public void SetSynchronizationContext(int hwnd)
+ {
+ _synchronizationContext = new FoxProSynchronizationContext(hwnd, this);
+ SynchronizationContext.SetSynchronizationContext(_synchronizationContext);
+ }
#region LoadAssembly Routines
@@ -696,14 +711,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 +980,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);
@@ -1465,7 +1498,7 @@ public void InvokeTaskMethodAsync(
ex = WrapException(LastException);
InvokeMethod_Internal(callBack, "onError", ex.Message, ex, method);
}
- });
+ }, TaskScheduler.FromCurrentSynchronizationContext());
}
catch (Exception ex)
{
@@ -1543,7 +1576,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
{
@@ -1560,7 +1593,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 a67d85b..c30aa34 100644
--- a/DotnetBridge/wwDotNetBridge.csproj
+++ b/DotnetBridge/wwDotNetBridge.csproj
@@ -32,6 +32,7 @@
https://github.com/RickStrahl/wwDotnetBridge
git
true
+ latest
$(NoWarn);CS1591;CS1572;CS1573
diff --git a/Tests/EventSubscriberTests.cs b/Tests/EventSubscriberTests.cs
index 7ce016b..33809b7 100644
--- a/Tests/EventSubscriberTests.cs
+++ b/Tests/EventSubscriberTests.cs
@@ -14,31 +14,46 @@ namespace wwDotnetBridge.Tests
[TestClass]
public class EventSubscriberTests
{
- [TestMethod]
- public void EventSubscriber_WaitForPastEvents()
+ private readonly wwDotNetBridge _bridge = new();
+ private readonly TaskCompletionSource