Skip to content

Commit

Permalink
Standardize on Win32MessagePump
Browse files Browse the repository at this point in the history
Also various cleanups/fixes to the pump itself. TWAIN functionality seems on par now with the previous WinForms implementation.
  • Loading branch information
cyanfish committed Sep 5, 2024
1 parent efbe474 commit 497b6bd
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 311 deletions.
14 changes: 6 additions & 8 deletions NAPS2.Lib.WinForms/EntryPoints/WindowsNativeWorkerEntryPoint.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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)
Expand Down
72 changes: 0 additions & 72 deletions NAPS2.Lib.WinForms/WinForms/WinFormsTwainHandleManager.cs

This file was deleted.

63 changes: 63 additions & 0 deletions NAPS2.Sdk/Platform/Windows/Win32.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
}
150 changes: 79 additions & 71 deletions NAPS2.Sdk/Platform/Windows/Win32MessagePump.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,57 +7,43 @@ 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<Action> _queue = new();
private readonly Thread? _messageLoopThread;
private bool _stopped;

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<Action>();
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<IntPtr, int, IntPtr, IntPtr, bool>? Filter { get; set; }

public ILogger Logger { get; set; } = NullLogger.Instance;

public IntPtr Handle { get; }
Expand All @@ -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();
}
Expand All @@ -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);
}
}

Expand All @@ -98,55 +84,77 @@ public T InvokeGet<T>(Func<T> 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<Action>();
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;
});
}
}
Loading

0 comments on commit 497b6bd

Please sign in to comment.