using System; using System.Runtime.InteropServices; using System.Threading; using Microsoft.Win32.SafeHandles; namespace MxGateway.Worker.Sta; public sealed class StaMessagePump { private const uint Infinite = 0xFFFFFFFF; private const uint MsgWaitFailed = 0xFFFFFFFF; private const uint MwmoInputAvailable = 0x0004; private const uint PmRemove = 0x0001; private const uint QsAllInput = 0x04FF; public void WaitForWorkOrMessages(WaitHandle commandWakeEvent, TimeSpan timeout) { if (commandWakeEvent is null) { throw new ArgumentNullException(nameof(commandWakeEvent)); } uint timeoutMilliseconds = ToTimeoutMilliseconds(timeout); SafeWaitHandle safeHandle = commandWakeEvent.SafeWaitHandle; IntPtr[] handles = [safeHandle.DangerousGetHandle()]; uint result = MsgWaitForMultipleObjectsEx( (uint)handles.Length, handles, timeoutMilliseconds, QsAllInput, MwmoInputAvailable); if (result == MsgWaitFailed) { throw new InvalidOperationException( "The worker STA message pump failed while waiting for command work or Windows messages."); } } public int PumpPendingMessages() { int pumpedMessages = 0; while (PeekMessage(out NativeMessage message, IntPtr.Zero, 0, 0, PmRemove)) { TranslateMessage(ref message); DispatchMessage(ref message); pumpedMessages++; } return pumpedMessages; } private static uint ToTimeoutMilliseconds(TimeSpan timeout) { if (timeout == Timeout.InfiniteTimeSpan) { return Infinite; } if (timeout <= TimeSpan.Zero) { return 0; } return timeout.TotalMilliseconds >= uint.MaxValue ? uint.MaxValue - 1 : (uint)Math.Ceiling(timeout.TotalMilliseconds); } [DllImport("user32.dll", SetLastError = true)] private static extern uint MsgWaitForMultipleObjectsEx( uint count, IntPtr[] handles, uint milliseconds, uint wakeMask, uint flags); [DllImport("user32.dll", SetLastError = true)] private static extern bool PeekMessage( out NativeMessage message, IntPtr windowHandle, uint messageFilterMin, uint messageFilterMax, uint removeMessage); [DllImport("user32.dll")] private static extern bool TranslateMessage(ref NativeMessage message); [DllImport("user32.dll")] private static extern IntPtr DispatchMessage(ref NativeMessage message); [StructLayout(LayoutKind.Sequential)] private struct NativeMessage { public IntPtr WindowHandle; public uint Message; public UIntPtr WParam; public IntPtr LParam; public uint Time; public NativePoint Point; } [StructLayout(LayoutKind.Sequential)] private struct NativePoint { public int X; public int Y; } }