diff --git a/NAPS2.Lib.WinForms/EntryPoints/WindowsNativeWorkerEntryPoint.cs b/NAPS2.Lib.WinForms/EntryPoints/WindowsNativeWorkerEntryPoint.cs index 62c1e992af..b1e85fd252 100644 --- a/NAPS2.Lib.WinForms/EntryPoints/WindowsNativeWorkerEntryPoint.cs +++ b/NAPS2.Lib.WinForms/EntryPoints/WindowsNativeWorkerEntryPoint.cs @@ -1,9 +1,8 @@ using System.Threading; using System.Windows.Forms; -using NAPS2.EtoForms.WinForms; using NAPS2.Modules; +using NAPS2.Platform.Windows; using NAPS2.Scan.Internal.Twain; -using NAPS2.WinForms; namespace NAPS2.EntryPoints; @@ -18,14 +17,13 @@ public static int Run(string[] args) Application.SetCompatibleTextRenderingDefault(false); Application.ThreadException += UnhandledException; - // Set up a form for the worker process - // A parent form is needed for some operations, namely 64-bit TWAIN scanning // TODO: We don't currently do TWAIN scanning in the native worker, so maybe this can be cleaned up - var form = new BackgroundForm(); - Invoker.Current = new WinFormsInvoker(() => form); - TwainHandleManager.Factory = () => new WinFormsTwainHandleManager(form); + var messagePump = Win32MessagePump.Create(); + // TODO: Set a logger on the message pump? + Invoker.Current = messagePump; + TwainHandleManager.Factory = () => new Win32TwainHandleManager(messagePump); - return WorkerEntryPoint.Run(args, new GdiModule(), () => Application.Run(form), () => form.Close()); + return WorkerEntryPoint.Run(args, new GdiModule(), messagePump.RunMessageLoop, messagePump.Dispose); } private static void UnhandledException(object? sender, ThreadExceptionEventArgs e) diff --git a/NAPS2.Lib.WinForms/WinForms/WinFormsTwainHandleManager.cs b/NAPS2.Lib.WinForms/WinForms/WinFormsTwainHandleManager.cs deleted file mode 100644 index 071d587fb2..0000000000 --- a/NAPS2.Lib.WinForms/WinForms/WinFormsTwainHandleManager.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Windows.Forms; -using NAPS2.Platform.Windows; -using NAPS2.Scan.Internal.Twain; - -namespace NAPS2.WinForms; - -internal class WinFormsTwainHandleManager : TwainHandleManager -{ - private readonly Form _baseForm; - private Form? _parentForm; - private IntPtr _disabledWindow; - private bool _disposed; - private IntPtr? _handle; - - public WinFormsTwainHandleManager(Form baseForm) - { - _baseForm = baseForm; - } - - public override IntPtr GetDsmHandle(IntPtr dialogParent, bool useNativeUi) - { - // This handle is used for the TWAIN event loop. However, in some cases (e.g. an early error) it can still - // be used for UI. - return _handle ??= GetHandle(dialogParent, useNativeUi); - } - - public override IntPtr GetEnableHandle(IntPtr dialogParent, bool useNativeUi) - { - // This handle is used as the parent window for TWAIN UI - return _handle ??= GetHandle(dialogParent, useNativeUi); - } - - private IntPtr GetHandle(IntPtr dialogParent, bool useNativeUi) - { - if (dialogParent == IntPtr.Zero) - { - // If we have no real parent, we just give it an arbitrary form handle in this process as a parent. - return _baseForm.Handle; - } - - // If we are expected to show UI, ideally we'd just return dialogParent. But I've found some issues with that - // where the window can become non-interactable (e.g. unable to cancel a native UI scan). The cause might be - // related to the window being in another process. - _parentForm = new BackgroundForm(); - - // At the Windows API level, a modal window is implemented by doing two things: - // 1. Setting the parent on the child window - // 2. Disabling the parent window - // We do this rather than calling ShowDialog to avoid blocking the thread. - _parentForm.Show(new Win32Window(dialogParent)); - if (useNativeUi) - { - // We only want to disable the parent window if we're showing the native UI. Otherwise, we expect that - // the NAPS2 UI should be interactable, and the only UI shown should be error messages. - Win32.EnableWindow(dialogParent, false); - } - _disabledWindow = dialogParent; - - return _parentForm.Handle; - } - - public override void Dispose() - { - if (_disposed) return; - _disposed = true; - _parentForm?.Close(); - if (_disabledWindow != IntPtr.Zero) - { - Win32.EnableWindow(_disabledWindow, true); - } - } -} \ No newline at end of file diff --git a/NAPS2.Sdk/Platform/Windows/Win32.cs b/NAPS2.Sdk/Platform/Windows/Win32.cs index 54fabbd651..cd20a6e274 100644 --- a/NAPS2.Sdk/Platform/Windows/Win32.cs +++ b/NAPS2.Sdk/Platform/Windows/Win32.cs @@ -47,6 +47,34 @@ internal static class Win32 [DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")] public static extern void CopyMemory(IntPtr dst, IntPtr src, uint len); + [DllImport("user32.dll")] + public static extern int GetMessage(out Message lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax); + + [DllImport("user32.dll")] + public static extern bool TranslateMessage(in Message lpmsg); + + [DllImport("user32.dll")] + public static extern bool DispatchMessage(ref Message lpmsg); + + [DllImport("user32.dll")] + public static extern bool PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr DefWindowProcW(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + + [DllImport("user32.dll")] + public static extern IntPtr CreateWindowEx(int dwExStyle, string lpClassName, string? lpWindowName, int dwStyle, + int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam); + + [DllImport("user32.dll")] + public static extern bool DestroyWindow(IntPtr hWnd); + + [DllImport("user32.dll", SetLastError = true)] + public static extern UInt16 RegisterClassW(in WndClass lpWndClass); + + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] + public static extern uint RegisterWindowMessage(string lpString); + public enum ShowWindowCommands { Hide = 0, @@ -63,4 +91,39 @@ public enum ShowWindowCommands ShowDefault = 10, ForceMinimize = 11 } + + public struct Message + { + public IntPtr hWnd; + public int msg; + public IntPtr wParam; + public IntPtr lParam; + public uint time; + public Point pt; + } + + public struct Point + { + public int x; + public int y; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct WndClass + { + public uint style; + public IntPtr lpfnWndProc; + public int cbClsExtra; + public int cbWndExtra; + public IntPtr hInstance; + public IntPtr hIcon; + public IntPtr hCursor; + public IntPtr hbrBackground; + [MarshalAs(UnmanagedType.LPWStr)] + public string lpszMenuName; + [MarshalAs(UnmanagedType.LPWStr)] + public string lpszClassName; + } + + public delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); } \ No newline at end of file diff --git a/NAPS2.Sdk/Platform/Windows/Win32MessagePump.cs b/NAPS2.Sdk/Platform/Windows/Win32MessagePump.cs index 5dea3f6110..56d7122c27 100644 --- a/NAPS2.Sdk/Platform/Windows/Win32MessagePump.cs +++ b/NAPS2.Sdk/Platform/Windows/Win32MessagePump.cs @@ -7,11 +7,19 @@ namespace NAPS2.Platform.Windows; internal class Win32MessagePump : IInvoker, IDisposable { + private const string WND_CLASS_NAME = "MPWndClass"; + private const string RUN_QUEUED_ACTIONS_MESSAGE_NAME = "MPRunQueuedActions"; + public static Win32MessagePump Create() { return new Win32MessagePump(); } + // We store the delegate as an instance variable so it doesn't get garbage collected + // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable + private readonly Win32.WndProc _wndProcDelegate; + private readonly uint _runQueuedActionsMessage; + private readonly Queue _queue = new(); private readonly Thread? _messageLoopThread; private bool _stopped; @@ -19,45 +27,23 @@ public static Win32MessagePump Create() private Win32MessagePump() { _messageLoopThread = Thread.CurrentThread; - Handle = CreateWindowEx(0, "Message", "", 0, 0, 0, 0, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); - } + _wndProcDelegate = CustomWndProc; - public void RunMessageLoop() - { - try + Win32.RegisterClassW(new Win32.WndClass { - while (!_stopped && GetMessage(out var msg, IntPtr.Zero, 0, 0) > 0) - { - DispatchMessage(ref msg); + lpszClassName = WND_CLASS_NAME, + lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate), + hInstance = Process.GetCurrentProcess().Handle + }); - var actionsToCall = new List(); - lock (_queue) - { - while (_queue.Count > 0) - { - actionsToCall.Add(_queue.Dequeue()); - } - } - // Run the actions outside the lock to avoid deadlock scenarios - foreach (var action in actionsToCall) - { - try - { - action(); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error in message handler"); - } - } - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Error in message loop"); - } + _runQueuedActionsMessage = Win32.RegisterWindowMessage(RUN_QUEUED_ACTIONS_MESSAGE_NAME); + + Handle = Win32.CreateWindowEx(0, WND_CLASS_NAME, "", 0, 0, 0, 0, 0, IntPtr.Zero, IntPtr.Zero, + Process.GetCurrentProcess().Handle, IntPtr.Zero); } + public Func? Filter { get; set; } + public ILogger Logger { get; set; } = NullLogger.Instance; public IntPtr Handle { get; } @@ -77,7 +63,7 @@ public void Invoke(Action action) action(); toggle.Set(); }); - PostMessage(Handle, 0, IntPtr.Zero, IntPtr.Zero); + Win32.PostMessage(Handle, _runQueuedActionsMessage, IntPtr.Zero, IntPtr.Zero); } toggle.WaitOne(); } @@ -87,7 +73,7 @@ public void InvokeDispatch(Action action) lock (_queue) { _queue.Enqueue(action); - PostMessage(Handle, 0, IntPtr.Zero, IntPtr.Zero); + Win32.PostMessage(Handle, _runQueuedActionsMessage, IntPtr.Zero, IntPtr.Zero); } } @@ -98,55 +84,77 @@ public T InvokeGet(Func func) return value; } - public IntPtr CreateBackgroundWindow(IntPtr parent = default) + public void RunMessageLoop() { - return InvokeGet(() => - CreateWindowEx(0, "Message", "", 0, 0, 0, 0, 0, parent, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero)); + try + { + while (!_stopped && Win32.GetMessage(out var msg, IntPtr.Zero, 0, 0) > 0) + { + if (!(Filter?.Invoke(Handle, msg.msg, msg.wParam, msg.lParam) ?? false)) + { + Win32.TranslateMessage(msg); + Win32.DispatchMessage(ref msg); + } + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Error in message loop"); + } } - public void CloseWindow(IntPtr window) + private void RunQueuedActions() { - InvokeDispatch(() => { DestroyWindow(window); }); + var actionsToCall = new List(); + lock (_queue) + { + while (_queue.Count > 0) + { + actionsToCall.Add(_queue.Dequeue()); + } + } + // Run the actions outside the lock to avoid deadlock scenarios + foreach (var action in actionsToCall) + { + try + { + action(); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error in invoked action"); + } + } } - public void Dispose() + private IntPtr CustomWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { - InvokeDispatch(() => + if (msg == _runQueuedActionsMessage) { - DestroyWindow(Handle); - _stopped = true; - }); + RunQueuedActions(); + return IntPtr.Zero; + } + return Win32.DefWindowProcW(hWnd, msg, wParam, lParam); } - private struct Message + public IntPtr CreateBackgroundWindow(IntPtr parent = default) { - public IntPtr hWnd; - public int msg; - public IntPtr wParam; - public IntPtr lParam; - public uint time; - public Point pt; + return InvokeGet(() => + Win32.CreateWindowEx(0, WND_CLASS_NAME, "", 0, 0, 0, 0, 0, parent, IntPtr.Zero, + Process.GetCurrentProcess().Handle, IntPtr.Zero)); } - private struct Point + public void CloseWindow(IntPtr window) { - public int x; - public int y; + InvokeDispatch(() => Win32.DestroyWindow(window)); } - [DllImport("user32.dll")] - private static extern int GetMessage(out Message lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax); - - [DllImport("user32.dll")] - private static extern bool DispatchMessage(ref Message lpmsg); - - [DllImport("user32.dll")] - private static extern bool PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); - - [DllImport("user32.dll")] - private static extern IntPtr CreateWindowEx(int dwExStyle, string lpClassName, string? lpWindowName, int dwStyle, - int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam); - - [DllImport("user32.dll")] - private static extern bool DestroyWindow(IntPtr hWnd); + public void Dispose() + { + InvokeDispatch(() => + { + Win32.DestroyWindow(Handle); + _stopped = true; + }); + } } \ No newline at end of file diff --git a/NAPS2.Sdk/Platform/Windows/Win32TwainHandleManager.cs b/NAPS2.Sdk/Platform/Windows/Win32TwainHandleManager.cs index 811ad4f583..ba29f0a84e 100644 --- a/NAPS2.Sdk/Platform/Windows/Win32TwainHandleManager.cs +++ b/NAPS2.Sdk/Platform/Windows/Win32TwainHandleManager.cs @@ -1,4 +1,5 @@ using NAPS2.Scan.Internal.Twain; +using NTwain; namespace NAPS2.Platform.Windows; @@ -56,6 +57,11 @@ private IntPtr GetHandle(IntPtr dialogParent, bool useNativeUi) return _parentWindow; } + public override MessageLoopHook CreateMessageLoopHook(IntPtr dialogParent = default, bool useNativeUi = false) + { + return new Win32MessageLoopHook(_messagePump, GetDsmHandle(dialogParent, useNativeUi)); + } + public override void Dispose() { if (_disposed) return; diff --git a/NAPS2.Sdk/Remoting/Worker/WorkerServer.cs b/NAPS2.Sdk/Remoting/Worker/WorkerServer.cs index b7340aae04..880864ba79 100644 --- a/NAPS2.Sdk/Remoting/Worker/WorkerServer.cs +++ b/NAPS2.Sdk/Remoting/Worker/WorkerServer.cs @@ -1,6 +1,7 @@ using System.Threading; using GrpcDotNetNamedPipes; using NAPS2.ImportExport.Email.Mapi; +using NAPS2.Platform.Windows; using NAPS2.Scan; using NAPS2.Scan.Internal.Twain; @@ -16,7 +17,11 @@ public static async Task Run(ScanningContext scanningContext, CancellationToken { try { - var tcs = new TaskCompletionSource(); + var messagePump = Win32MessagePump.Create(); + messagePump.Logger = scanningContext.Logger; + Invoker.Current = messagePump; + TwainHandleManager.Factory = () => new Win32TwainHandleManager(messagePump); + var server = new NamedPipeServer(string.Format(WorkerFactory.PIPE_NAME_FORMAT, Process.GetCurrentProcess().Id)); var serviceImpl = new WorkerServiceImpl(scanningContext, @@ -26,14 +31,14 @@ public static async Task Run(ScanningContext scanningContext, CancellationToken #else new LocalTwainController(scanningContext)); #endif - serviceImpl.OnStop += (_, _) => tcs.SetResult(true); + serviceImpl.OnStop += (_, _) => messagePump.Dispose(); WorkerService.BindService(server.ServiceBinder, serviceImpl); cancellationToken.Register(() => serviceImpl.Stop()); server.Start(); try { Console.WriteLine(@"ready"); - await tcs.Task; + messagePump.RunMessageLoop(); } finally { diff --git a/NAPS2.Sdk/Scan/Internal/Twain/LocalTwainController.cs b/NAPS2.Sdk/Scan/Internal/Twain/LocalTwainController.cs index 80982fdd89..46b2c7f2b6 100644 --- a/NAPS2.Sdk/Scan/Internal/Twain/LocalTwainController.cs +++ b/NAPS2.Sdk/Scan/Internal/Twain/LocalTwainController.cs @@ -61,13 +61,7 @@ private List InternalGetDeviceList(ScanOptions options) { PlatformInfo.Current.PreferNewDSM = options.TwainOptions.Dsm != TwainDsm.Old; var session = new TwainSession(TwainAppId); - // TODO: Standardize on custom hook? -#if NET6_0_OR_GREATER - if (!OperatingSystem.IsWindows()) throw new InvalidOperationException("Windows-only"); - session.Open(new Win32MessageLoopHook(_logger)); -#else - session.Open(); -#endif + session.Open(TwainHandleManager.Factory().CreateMessageLoopHook()); try { return session.GetSources().Select(ds => new ScanDevice(Driver.Twain, ds.Name, ds.Name)).ToList(); @@ -109,13 +103,7 @@ private ScanCaps InternalGetCaps(ScanOptions options) { PlatformInfo.Current.PreferNewDSM = options.TwainOptions.Dsm != TwainDsm.Old; var session = new TwainSession(TwainAppId); - // TODO: Standardize on custom hook? -#if NET6_0_OR_GREATER - if (!OperatingSystem.IsWindows()) throw new InvalidOperationException("Windows-only"); - session.Open(new Win32MessageLoopHook(_logger)); -#else - session.Open(); -#endif + session.Open(TwainHandleManager.Factory().CreateMessageLoopHook()); try { var ds = session.GetSources().FirstOrDefault(ds => ds.Name == options.Device!.ID); diff --git a/NAPS2.Sdk/Scan/Internal/Twain/TwainHandleManager.cs b/NAPS2.Sdk/Scan/Internal/Twain/TwainHandleManager.cs index b1250315a0..7b2bc8fe8c 100644 --- a/NAPS2.Sdk/Scan/Internal/Twain/TwainHandleManager.cs +++ b/NAPS2.Sdk/Scan/Internal/Twain/TwainHandleManager.cs @@ -1,3 +1,5 @@ +using NTwain; + namespace NAPS2.Scan.Internal.Twain; internal class TwainHandleManager : IDisposable @@ -8,15 +10,12 @@ protected TwainHandleManager() { } - public virtual IntPtr GetDsmHandle(IntPtr dialogParent, bool useNativeUi) - { - return dialogParent; - } + public virtual IntPtr GetDsmHandle(IntPtr dialogParent, bool useNativeUi) => dialogParent; - public virtual IntPtr GetEnableHandle(IntPtr dialogParent, bool useNativeUi) - { - return dialogParent; - } + public virtual IntPtr GetEnableHandle(IntPtr dialogParent, bool useNativeUi) => dialogParent; + + public virtual MessageLoopHook CreateMessageLoopHook(IntPtr dialogParent = default, bool useNativeUi = false) => + throw new NotSupportedException(); public virtual void Dispose() { diff --git a/NAPS2.Sdk/Scan/Internal/Twain/TwainScanRunner.cs b/NAPS2.Sdk/Scan/Internal/Twain/TwainScanRunner.cs index 7f46a9449c..61f0268ec5 100644 --- a/NAPS2.Sdk/Scan/Internal/Twain/TwainScanRunner.cs +++ b/NAPS2.Sdk/Scan/Internal/Twain/TwainScanRunner.cs @@ -62,16 +62,8 @@ private void Init() try { _logger.LogDebug("NAPS2.TW - Opening session"); - var dsmHandle = _handleManager.GetDsmHandle(_options.DialogParent, _options.UseNativeUI || _options.TwainOptions.ShowProgress); -#if NET6_0_OR_GREATER - - if (!OperatingSystem.IsWindows()) throw new InvalidOperationException("Windows-only"); - var rc = _session.Open(new Win32MessageLoopHook(_logger)); -#else - var rc = dsmHandle == IntPtr.Zero - ? _session.Open() - : _session.Open(new WindowsFormsMessageLoopHook(dsmHandle)); -#endif + bool useNativeUi = _options.UseNativeUI || _options.TwainOptions.ShowProgress; + var rc = _session.Open(_handleManager.CreateMessageLoopHook(_options.DialogParent, useNativeUi)); if (rc != ReturnCode.Success) { throw new DeviceException($"TWAIN session open error: {rc}"); @@ -99,7 +91,7 @@ private void Init() _logger.LogDebug("NAPS2.TW - Enabling source"); var ui = _options.UseNativeUI ? SourceEnableMode.ShowUI : SourceEnableMode.NoUI; - var enableHandle = _handleManager.GetEnableHandle(_options.DialogParent, _options.UseNativeUI || _options.TwainOptions.ShowProgress); + var enableHandle = _handleManager.GetEnableHandle(_options.DialogParent, useNativeUi); // Note that according to the twain spec, on Windows it is recommended to set the modal parameter to false rc = _source.Enable(ui, false, enableHandle); if (rc != ReturnCode.Success) @@ -122,7 +114,7 @@ private void FinishWithCancellation() if (_session.State != 5) { // If we're in state 6 or 7, this will abort the ongoing transfer via ForceStepDown. - // If we're in state 4 or lower, then we're not transferring and this will just clean up the source/session. + // If we're in state 4 or lower, then we're not transferring and this will just clean up the source/session. UnloadTwain(); _tcs.TrySetResult(false); } diff --git a/NAPS2.Sdk/Scan/Internal/Twain/Win32MessageLoopHook.cs b/NAPS2.Sdk/Scan/Internal/Twain/Win32MessageLoopHook.cs index ab1c49b0eb..857fee0827 100644 --- a/NAPS2.Sdk/Scan/Internal/Twain/Win32MessageLoopHook.cs +++ b/NAPS2.Sdk/Scan/Internal/Twain/Win32MessageLoopHook.cs @@ -1,143 +1,29 @@ #if !MAC -using System.Runtime.InteropServices; -using System.Threading; -using Microsoft.Extensions.Logging; +using NAPS2.Platform.Windows; using NTwain; namespace NAPS2.Scan.Internal.Twain; -// TODO: Consider refactoring this to re-use code from Win32MessagePump /// /// A MessageLoopHook implementation that uses Win32 methods directly, with no dependencies on WinForms or WPF. /// [System.Runtime.Versioning.SupportedOSPlatform("windows")] internal class Win32MessageLoopHook : MessageLoopHook { - private readonly ILogger _logger; - private readonly Queue _queue = new(); - private bool _stopped; - private Thread? _messageLoopThread; + private readonly Win32MessagePump _messagePump; - public Win32MessageLoopHook(ILogger logger) + public Win32MessageLoopHook(Win32MessagePump messagePump, IntPtr dsmHandle) { - _logger = logger; + _messagePump = messagePump; + Handle = dsmHandle; } - public override void Invoke(Action action) - { - if (Thread.CurrentThread == _messageLoopThread) - { - action(); - return; - } - var toggle = new ManualResetEvent(false); - lock (_queue) - { - _queue.Enqueue(() => - { - action(); - toggle.Set(); - }); - PostMessage(Handle, 0, IntPtr.Zero, IntPtr.Zero); - } - toggle.WaitOne(); - } - - public override void BeginInvoke(Action action) - { - lock (_queue) - { - _queue.Enqueue(action); - PostMessage(Handle, 0, IntPtr.Zero, IntPtr.Zero); - } - } - - protected override void Start(IWinMessageFilter filter) - { - _messageLoopThread = new Thread(() => - { - try - { - Handle = CreateWindowEx(0, "Message", null, 0, 0, 0, 0, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, - IntPtr.Zero); - while (!_stopped && GetMessage(out var msg, IntPtr.Zero, 0, 0) > 0) - { - if (!filter.IsTwainMessage(Handle, msg.msg, msg.wParam, msg.lParam)) - { - DispatchMessage(ref msg); - } - - var actionsToCall = new List(); - lock (_queue) - { - while (_queue.Count > 0) - { - actionsToCall.Add(_queue.Dequeue()); - } - } - // Run the actions outside the lock to avoid deadlock scenarios - foreach (var action in actionsToCall) - { - try - { - action(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error in TWAIN message handler"); - } - } - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error in TWAIN message loop"); - } - }); - _messageLoopThread.IsBackground = true; - _messageLoopThread.SetApartmentState(ApartmentState.STA); - _messageLoopThread.Start(); - } - - protected override void Stop() - { - BeginInvoke(() => - { - DestroyWindow(Handle); - _stopped = true; - }); - } - - private struct Message - { - public IntPtr hWnd; - public int msg; - public IntPtr wParam; - public IntPtr lParam; - public uint time; - public Point pt; - } - - private struct Point - { - public int x; - public int y; - } - - [DllImport("user32.dll")] - private static extern int GetMessage(out Message lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax); - - [DllImport("user32.dll")] - private static extern bool DispatchMessage(ref Message lpmsg); + public override void Invoke(Action action) => _messagePump.Invoke(action); - [DllImport("user32.dll")] - private static extern bool PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + public override void BeginInvoke(Action action) => _messagePump.InvokeDispatch(action); - [DllImport("user32.dll")] - private static extern IntPtr CreateWindowEx(int dwExStyle, string lpClassName, string? lpWindowName, int dwStyle, - int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam); + protected override void Start(IWinMessageFilter filter) => _messagePump.Filter = filter.IsTwainMessage; - [DllImport("user32.dll")] - private static extern bool DestroyWindow(IntPtr hWnd); + protected override void Stop() => _messagePump.Filter = null; } #endif \ No newline at end of file