Merge pull request #77 from soukoku/remove-winform-test

Remove winform and wpf dep
This commit is contained in:
Eugene Wang
2026-01-01 23:36:31 -05:00
committed by GitHub
11 changed files with 722 additions and 260 deletions

View File

@@ -21,7 +21,6 @@ namespace ScannerTester
_twain.TransferReady += _twain_TransferReady;
_twain.Transferred += _twain_Transferred;
_twain.SourceDisabled += _twain_SourceDisabled;
_twain.AddWinformFilter();
}
private void _twain_SourceDisabled(TwainAppSession sender, TW_IDENTITY_LEGACY e)
@@ -32,14 +31,12 @@ namespace ScannerTester
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_twain.OpenDSM(Handle, SynchronizationContext.Current!);
//_ = _twain.OpenDSMAsync();
_ = _twain.OpenDSMAsync();
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
_twain.CloseDSM();
//_ = _twain.CloseDSMAsync();
_ = _twain.CloseDSMAsync();
base.OnFormClosed(e);
}

View File

@@ -90,25 +90,14 @@ public partial class Form1 : Form
{
base.OnHandleCreated(e);
if (useDiyPump)
{
var sts = await twain.OpenDSMAsync();
Debug.WriteLine($"OpenDSMAsync={sts}");
}
else
{
var hwnd = this.Handle;
var sts = twain.OpenDSM(hwnd, SynchronizationContext.Current!);
twain.AddWinformFilter();
Debug.WriteLine($"OpenDSM={sts}");
}
var sts = await twain.OpenDSMAsync();
Debug.WriteLine($"OpenDSMAsync={sts}");
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
var finalState = twain.TryStepdown(STATE.S2);
Debug.WriteLine($"Stepdown result state={finalState}");
twain.RemoveWinformFilter();
base.OnFormClosing(e);
}

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<!--change these in each release-->
<VersionPrefix>4.0.0.0</VersionPrefix>
<VersionSuffix>alpha.31</VersionSuffix>
<VersionSuffix>alpha.40</VersionSuffix>
<!--keep it the same until major # changes-->
<AssemblyVersion>4.0.0.0</AssemblyVersion>

View File

@@ -1,173 +1,121 @@
#if WINDOWS || NETFRAMEWORK
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
using NTwain.Data;
using NTwain.Native;
using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace NTwain
namespace NTwain;
/// <summary>
/// For use under Windows to host a message pump.
/// </summary>
#if !NETFRAMEWORK
[SupportedOSPlatform("windows5.1.2600")]
#endif
class MessagePumpThread
{
Win32MessagePump? _pump;
TwainAppSession? _twain;
public bool IsRunning => _pump != null && !_pump.MainWindow.IsNull;
/// <summary>
/// For use under Windows to host a message pump.
/// Starts the thread, attaches a twain session to it,
/// and opens the DSM.
/// </summary>
class MessagePumpThread
/// <param name="twain"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
public async Task<STS> AttachAsync(TwainAppSession twain)
{
KeepAliveForm? _dummyForm;
TwainAppSession? _twain;
if (_twain != null) return new STS { RC = TWRC.SUCCESS };
public bool IsRunning => _dummyForm != null && _dummyForm.IsHandleCreated;
Thread t = new(RunMessagePump);
t.IsBackground = true;
t.SetApartmentState(ApartmentState.STA);
t.Start();
/// <summary>
/// Starts the thread, attaches a twain session to it,
/// and opens the DSM.
/// </summary>
/// <param name="twain"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
public async Task<STS> AttachAsync(TwainAppSession twain)
while (_pump == null || _pump.MainWindow.IsNull)
{
if (twain.State > STATE.S2) throw new InvalidOperationException("Cannot attach to an opened TWAIN session.");
if (_twain != null || _dummyForm != null) throw new InvalidOperationException("Already attached previously.");
await Task.Delay(50);
}
Thread t = new(RunMessagePump);
t.IsBackground = true;
t.SetApartmentState(ApartmentState.STA);
t.Start();
while (_dummyForm == null || !_dummyForm.IsHandleCreated)
STS sts = default;
TaskCompletionSource<bool> tcs = new();
_pump.PostToUIThread(() =>
{
try
{
await Task.Delay(50);
sts = twain.OpenDSM(_pump.MainWindow, SynchronizationContext.Current!);
if (sts.IsSuccess)
{
_pump.AddMessageFilter(twain);
_twain = twain;
}
else
{
_pump.Quit();
_pump = null;
}
}
STS sts = default;
TaskCompletionSource<bool> tcs = new();
_dummyForm.BeginInvoke(() =>
finally
{
try
tcs.TrySetResult(true);
}
});
await tcs.Task;
return sts;
}
/// <summary>
/// Detatches a previously attached session and stops the thread.
/// </summary>
public async Task<STS> DetachAsync()
{
STS sts = default;
if (_pump != null && _twain != null)
{
TaskCompletionSource<STS> tcs = new();
_pump.PostToUIThread(() =>
{
if (_twain == null) return;
sts = _twain.CloseDSMReal();
if (sts.IsSuccess)
{
sts = twain.OpenDSM(_dummyForm.Handle, SynchronizationContext.Current!);
if (sts.IsSuccess)
{
twain.AddWinformFilter();
_twain = twain;
}
else
{
_dummyForm.Close(true);
}
}
finally
{
tcs.TrySetResult(true);
_pump.RemoveMessageFilter(_twain);
_pump.Quit();
_twain = null;
}
tcs.SetResult(sts);
});
await tcs.Task;
return sts;
}
/// <summary>
/// Detatches a previously attached session and stops the thread.
/// </summary>
public async Task<STS> DetachAsync()
{
STS sts = default;
if (_dummyForm != null && _twain != null)
{
TaskCompletionSource<STS> tcs = new();
_dummyForm.BeginInvoke(() =>
{
sts = _twain.CloseDSMReal();
if (sts.IsSuccess)
{
_twain.RemoveWinformFilter();
_dummyForm.Close(true);
_twain = null;
}
tcs.SetResult(sts);
});
await tcs.Task;
}
return sts;
}
public void BringWindowToFront()
{
if (_dummyForm != null)
{
_dummyForm.BeginInvoke(_dummyForm.BringToFront);
}
}
void RunMessagePump()
{
_twain?.Logger.LogDebug("Starting TWAIN message pump thread.");
Application.ThreadException += Application_ThreadException;
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
_dummyForm = new KeepAliveForm();
_dummyForm.FormClosed += (s, e) =>
{
_dummyForm = null;
};
Application.Run(_dummyForm);
_twain?.Logger.LogDebug("TWAIN message pump thread exiting.");
}
private void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
{
_twain?.Logger.LogError(e.Exception, "Unhandled exception in TWAIN message pump thread.");
}
class KeepAliveForm : Form
{
public KeepAliveForm()
{
ShowInTaskbar = false;
}
[DllImport("user32.dll")]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
SetParent(this.Handle, new IntPtr(-3)); // HWND_MESSAGE
}
//protected override CreateParams CreateParams
//{
// get
// {
// CreateParams cp = base.CreateParams;
// cp.ExStyle |= 0x80; // WS_EX_TOOLWINDOW
// return cp;
// }
//}
//protected override void OnShown(EventArgs e)
//{
// Hide();
// base.OnShown(e);
//}
bool _closeForReal = false;
internal void Close(bool forReal)
{
_closeForReal = forReal;
Close();
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing && !_closeForReal)
{
e.Cancel = true;
Hide();
}
base.OnFormClosing(e);
}
}
return sts;
}
}
#endif
//public void BringWindowToFront()
//{
// if (_dummyForm != null)
// {
// _dummyForm.BeginInvoke(_dummyForm.BringToFront);
// }
//}
void RunMessagePump()
{
_twain?.Logger.LogDebug("Starting TWAIN message pump thread.");
_pump = new Win32MessagePump();
_pump.UnhandledException += Application_ThreadException;
_pump.Run();
_twain?.Logger.LogDebug("TWAIN message pump thread exiting.");
}
private void Application_ThreadException(object? sender, Win32MessagePumpExceptionEventArgs e)
{
_twain?.Logger.LogError(e.Exception, "Unhandled exception in TWAIN message pump thread from {Source}.", e.Source);
e.Handled = true;
}
}

View File

@@ -3,15 +3,16 @@
<PropertyGroup>
<PackageId>NTwain</PackageId>
<Description>Library containing the TWAIN API for dotnet.</Description>
<TargetFrameworks>net8.0;net8.0-windows;net9.0;net9.0-windows;net10.0;net10.0-windows;net472;</TargetFrameworks>
<!--<TargetFrameworks>net8.0;net8.0-windows;net9.0;net9.0-windows;net10.0;net10.0-windows;net472;</TargetFrameworks>-->
<TargetFrameworks>net8.0;net9.0;net10.0;net472;</TargetFrameworks>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net472' OR $(TargetFramework.EndsWith('windows'))">
<!--<PropertyGroup Condition=" '$(TargetFramework)' == 'net472' OR $(TargetFramework.EndsWith('windows'))">
<UseWindowsForms>true</UseWindowsForms>
<UseWPF>true</UseWPF>
</PropertyGroup>
</PropertyGroup>-->
<ItemGroup Condition=" '$(TargetFramework)' == 'net472'">
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />

View File

@@ -0,0 +1,517 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Gdi;
using Windows.Win32.UI.WindowsAndMessaging;
namespace NTwain.Native;
// this piece was mostly generated by AI
#if !NETFRAMEWORK
[SupportedOSPlatform("windows5.1.2600")]
#endif
internal sealed class Win32MessagePump : IDisposable
{
private const string WindowClassName = "MsgPumpParkWindow";
private const uint WM_APP_INVOKE = PInvoke.WM_APP + 1;
private readonly FreeLibrarySafeHandle _hInstance;
private readonly uint _threadId;
private HWND _mainWindow;
private bool _disposed;
// Store the delegate to prevent garbage collection
private static WNDPROC? s_wndProc;
// Queue for work items posted to the UI thread
private readonly Queue<Action> _workQueue = new();
private readonly object _workQueueLock = new();
// Message filters
private readonly List<IWin32MessageFilter> _messageFilters = new();
private readonly object _messageFiltersLock = new();
// SynchronizationContext
private Win32SynchronizationContext? _synchronizationContext;
public Win32MessagePump()
{
_hInstance = PInvoke.GetModuleHandle((string?)null);
_threadId = PInvoke.GetCurrentThreadId();
}
/// <summary>
/// Occurs when an unhandled exception is thrown during message processing.
/// </summary>
public event EventHandler<Win32MessagePumpExceptionEventArgs>? UnhandledException;
/// <summary>
/// Gets the main (hidden) message window handle.
/// </summary>
public HWND MainWindow => _mainWindow;
public bool InvokeRequired => PInvoke.GetCurrentThreadId() != _threadId;
/// <summary>
/// Gets the SynchronizationContext associated with this message pump.
/// </summary>
public SynchronizationContext? SynchronizationContext => _synchronizationContext;
/// <summary>
/// Adds a message filter to the application's message pump.
/// </summary>
public void AddMessageFilter(IWin32MessageFilter filter)
{
lock (_messageFiltersLock)
{
_messageFilters.Add(filter);
}
}
/// <summary>
/// Removes a message filter from the application's message pump.
/// </summary>
public bool RemoveMessageFilter(IWin32MessageFilter filter)
{
lock (_messageFiltersLock)
{
return _messageFilters.Remove(filter);
}
}
public int Run()
{
if (!RegisterWindowClass())
{
return -1;
}
if (!CreateMainWindow())
{
UnregisterWindowClass();
return -1;
}
// Create and install the SynchronizationContext
_synchronizationContext = new Win32SynchronizationContext(this);
SynchronizationContext.SetSynchronizationContext(_synchronizationContext);
int exitCode = RunMessageLoop();
// Clear the SynchronizationContext
SynchronizationContext.SetSynchronizationContext(null);
Dispose();
return exitCode;
}
private bool RegisterWindowClass()
{
s_wndProc = WindowProc;
unsafe
{
fixed (char* className = WindowClassName)
{
var wc = new WNDCLASSEXW
{
cbSize = (uint)Marshal.SizeOf<WNDCLASSEXW>(),
style = 0,
lpfnWndProc = s_wndProc,
cbClsExtra = 0,
cbWndExtra = 0,
hInstance = (HINSTANCE)_hInstance.DangerousGetHandle(),
hIcon = HICON.Null,
hCursor = HCURSOR.Null,
hbrBackground = HBRUSH.Null,
lpszMenuName = null,
lpszClassName = className,
hIconSm = HICON.Null
};
ushort atom = PInvoke.RegisterClassEx(in wc);
if (atom == 0)
{
return false;
}
}
}
return true;
}
private void UnregisterWindowClass()
{
PInvoke.UnregisterClass(WindowClassName, _hInstance);
}
private bool CreateMainWindow()
{
unsafe
{
_mainWindow = PInvoke.CreateWindowEx(
0,
WindowClassName,
"MsgPump Window",
0,
0, 0, 0, 0,
new HWND(unchecked((nint)(-3))), // HWND_MESSAGE
null,
_hInstance,
null);
}
return !_mainWindow.IsNull;
}
private int RunMessageLoop()
{
MSG msg;
while (true)
{
int result;
unsafe
{
result = PInvoke.GetMessage(&msg, HWND.Null, 0, 0);
}
if (result == 0) // WM_QUIT
{
return (int)msg.wParam.Value;
}
if (result == -1) // Error
{
return -1;
}
ProcessWorkQueue();
if (FilterMessage(ref msg))
{
continue;
}
PInvoke.TranslateMessage(in msg);
PInvoke.DispatchMessage(in msg);
}
}
private bool FilterMessage(ref MSG msg)
{
var message = Win32Message.FromMSG(in msg);
lock (_messageFiltersLock)
{
foreach (var filter in _messageFilters)
{
try
{
if (filter.PreFilterMessage(ref message))
{
return true;
}
}
catch (Exception ex)
{
OnUnhandledException(ex, ExceptionSource.MessageFilter);
}
}
}
return false;
}
/// <summary>
/// Post work to be executed on the UI thread.
/// Can be called from any thread.
/// </summary>
public void PostToUIThread(Action action)
{
if (InvokeRequired)
{
lock (_workQueueLock)
{
_workQueue.Enqueue(action);
}
if (!_mainWindow.IsNull)
{
PInvoke.PostMessage(_mainWindow, WM_APP_INVOKE, 0, 0);
}
}
else
{
action();
}
}
/// <summary>
/// Posts a quit message to terminate the message loop.
/// </summary>
public void Quit(int exitCode = 0)
{
if (InvokeRequired)
{
PostToUIThread(() =>
{
PInvoke.PostQuitMessage(exitCode);
});
}
else
{
PInvoke.PostQuitMessage(exitCode);
}
}
private void ProcessWorkQueue()
{
while (true)
{
Action? action;
lock (_workQueueLock)
{
if (_workQueue.Count == 0)
break;
action = _workQueue.Dequeue();
}
try
{
action?.Invoke();
}
catch (Exception ex)
{
OnUnhandledException(ex, ExceptionSource.WorkQueue);
}
}
}
/// <summary>
/// Raises the UnhandledException event.
/// </summary>
internal void OnUnhandledException(Exception exception, ExceptionSource source)
{
var args = new Win32MessagePumpExceptionEventArgs(exception, source);
UnhandledException?.Invoke(this, args);
if (!args.Handled)
{
System.Diagnostics.Debug.WriteLine($"{source} exception: {exception}");
}
}
private static LRESULT WindowProc(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam)
{
if (msg == PInvoke.WM_DESTROY)
{
PInvoke.PostQuitMessage(0);
return new LRESULT(0);
}
return PInvoke.DefWindowProc(hwnd, msg, wParam, lParam);
}
public void Dispose()
{
if (_disposed)
return;
_disposed = true;
if (!_mainWindow.IsNull)
{
PInvoke.DestroyWindow(_mainWindow);
_mainWindow = HWND.Null;
}
UnregisterWindowClass();
}
}
/// <summary>
/// SynchronizationContext implementation for Win32MessagePump that integrates
/// with async/await and other .NET frameworks.
/// </summary>
#if !NETFRAMEWORK
[SupportedOSPlatform("windows5.1.2600")]
#endif
internal sealed class Win32SynchronizationContext : SynchronizationContext
{
private readonly Win32MessagePump _messagePump;
public Win32SynchronizationContext(Win32MessagePump messagePump)
{
_messagePump = messagePump ?? throw new ArgumentNullException(nameof(messagePump));
}
/// <summary>
/// Dispatches an asynchronous message to the message pump thread.
/// </summary>
public override void Post(SendOrPostCallback d, object? state)
{
if (d == null) return;
_messagePump.PostToUIThread(() =>
{
try
{
d(state);
}
catch (Exception ex)
{
_messagePump.OnUnhandledException(ex, ExceptionSource.SynchronizationContext);
}
});
}
/// <summary>
/// Dispatches a synchronous message to the message pump thread.
/// This is not recommended for general use as it can cause deadlocks.
/// </summary>
public override void Send(SendOrPostCallback d, object? state)
{
if (d == null) return;
if (!_messagePump.InvokeRequired)
{
// Already on the UI thread
try
{
d(state);
}
catch (Exception ex)
{
_messagePump.OnUnhandledException(ex, ExceptionSource.SynchronizationContext);
throw;
}
return;
}
// Send is synchronous - we need to block until the callback completes
// This is dangerous and can cause deadlocks, but it's part of the contract
using var completed = new ManualResetEventSlim(false);
Exception? exception = null;
_messagePump.PostToUIThread(() =>
{
try
{
d(state);
}
catch (Exception ex)
{
exception = ex;
_messagePump.OnUnhandledException(ex, ExceptionSource.SynchronizationContext);
}
finally
{
completed.Set();
}
});
completed.Wait();
if (exception != null)
{
throw new InvalidOperationException("Exception occurred in Send operation", exception);
}
}
/// <summary>
/// Creates a copy of the synchronization context.
/// </summary>
public override SynchronizationContext CreateCopy()
{
return new Win32SynchronizationContext(_messagePump);
}
}
/// <summary>
/// Defines a message filter interface that allows external code to participate
/// in the message loop processing, similar to WinForms' IMessageFilter.
/// </summary>
public interface IWin32MessageFilter
{
/// <summary>
/// Filters a message before it is dispatched.
/// </summary>
/// <param name="message">The message to filter.</param>
/// <returns>true to filter the message and stop it from being dispatched;
/// false to allow the message to continue to the next filter or be dispatched.</returns>
bool PreFilterMessage(ref Win32Message message);
}
/// <summary>
/// Represents a Windows message for use with IMessageFilter.
/// </summary>
public struct Win32Message
{
public nint HWnd;
public uint Msg;
public nuint WParam;
public nint LParam;
internal static Win32Message FromMSG(in MSG msg) => new()
{
HWnd = msg.hwnd,
Msg = msg.message,
WParam = msg.wParam,
LParam = msg.lParam
};
}
/// <summary>
/// Event args for unhandled exceptions in the message pump.
/// </summary>
public class Win32MessagePumpExceptionEventArgs : EventArgs
{
public Win32MessagePumpExceptionEventArgs(Exception exception, ExceptionSource source)
{
Exception = exception;
Source = source;
}
/// <summary>
/// Gets the exception that occurred.
/// </summary>
public Exception Exception { get; }
/// <summary>
/// Gets the source where the exception occurred.
/// </summary>
public ExceptionSource Source { get; }
/// <summary>
/// Gets or sets whether the exception has been handled.
/// If false, the exception will be logged to Debug output.
/// </summary>
public bool Handled { get; set; }
}
/// <summary>
/// Indicates where an exception originated in the message pump.
/// </summary>
public enum ExceptionSource
{
/// <summary>
/// Exception occurred in a message filter.
/// </summary>
MessageFilter,
/// <summary>
/// Exception occurred in a work queue item.
/// </summary>
WorkQueue,
/// <summary>
/// Exception occurred in the SynchronizationContext.
/// </summary>
SynchronizationContext
}

View File

@@ -1,4 +0,0 @@
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"allowMarshaling": false
}

View File

@@ -6,3 +6,25 @@ GlobalUnlock
BITMAPINFOHEADER
BITMAPINFO
BITMAPFILEHEADER
// Window class and creation
RegisterClassEx
UnregisterClass
CreateWindowEx
DestroyWindow
DefWindowProc
// Message loop
GetMessage
TranslateMessage
DispatchMessage
PostQuitMessage
PostMessage
// System
GetModuleHandle
GetCurrentThreadId
// Constants - Window Messages
WM_DESTROY
WM_APP

View File

@@ -0,0 +1,47 @@
#if NETFRAMEWORK
using System;
namespace NTwain;
/// <summary>
/// Hack to make it build without preprocessor directives all over.
/// </summary>
static class OperatingSystem
{
static readonly bool _isWindows = Environment.OSVersion.Platform == PlatformID.Win32NT;
public static bool IsWindows() => _isWindows;
// copied from .net logic
public static bool IsWindowsVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0)
=> IsWindows() && IsOSVersionAtLeast(major, minor, build, revision);
private static bool IsOSVersionAtLeast(int major, int minor, int build, int revision)
{
Version current = Environment.OSVersion.Version;
if (current.Major != major)
{
return current.Major > major;
}
if (current.Minor != minor)
{
return current.Minor > minor;
}
// Unspecified build component is to be treated as zero
int currentBuild = current.Build < 0 ? 0 : current.Build;
build = build < 0 ? 0 : build;
if (currentBuild != build)
{
return currentBuild > build;
}
// Unspecified revision component is to be treated as zero
int currentRevision = current.Revision < 0 ? 0 : current.Revision;
revision = revision < 0 ? 0 : revision;
return currentRevision >= revision;
}
}
#endif

View File

@@ -1,13 +1,8 @@
#if WINDOWS || NETFRAMEWORK
using Microsoft.Extensions.Logging;
using NTwain.Data;
using NTwain.Data;
using NTwain.Native;
using NTwain.Triplets;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Windows.Interop;
using Windows.Win32.Foundation;
using MSG = NTwain.Data.MSG;
@@ -16,59 +11,12 @@ namespace NTwain
// contains parts for winform/wpf message loop integration
partial class TwainAppSession : IMessageFilter
partial class TwainAppSession : IWin32MessageFilter
{
HwndSource? _wpfhook;
/// <summary>
/// Registers this session for use in a Winform UI thread.
/// </summary>
public void AddWinformFilter()
bool IWin32MessageFilter.PreFilterMessage(ref Win32Message m)
{
Application.AddMessageFilter(this);
}
/// <summary>
/// Unregisters this session if previously registered with <see cref="AddWinformFilter"/>.
/// </summary>
public void RemoveWinformFilter()
{
Application.RemoveMessageFilter(this);
}
/// <summary>
/// Registers this session for use in a WPF UI thread.
/// This requires the hwnd used in <see cref="OpenDSM"/>
/// be a valid WPF window handle.
/// </summary>
public void AddWpfHook()
{
if (_wpfhook == null)
{
_wpfhook = HwndSource.FromHwnd(_hwnd);
_wpfhook.AddHook(WpfHook);
}
}
/// <summary>
/// Unregisters this session if previously registered with <see cref="AddWpfHook"/>.
/// </summary>
public void RemoveWpfHook()
{
if (_wpfhook != null)
{
_wpfhook.RemoveHook(WpfHook);
_wpfhook = null;
}
}
bool IMessageFilter.PreFilterMessage(ref Message m)
{
return WndProc(m.HWnd, m.Msg, m.WParam, m.LParam);
}
IntPtr WpfHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
handled = WndProc(hwnd, msg, wParam, lParam);
return IntPtr.Zero;
return WndProc(m.HWnd, (int)m.Msg, (nint)m.WParam, m.LParam);
}
/// <summary>
@@ -112,5 +60,4 @@ namespace NTwain
return handled;
}
}
}
#endif
}

View File

@@ -3,10 +3,8 @@ using Microsoft.Extensions.Logging.Abstractions;
using NTwain.Data;
using NTwain.Triplets;
using System;
using System.IO.Packaging;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
@@ -32,9 +30,10 @@ namespace NTwain
{
if (logger != null) _logger = logger;
#if WINDOWS || NETFRAMEWORK
DSM.DsmLoader.TryLoadCustomDSM(Logger);
#endif
if (OperatingSystem.IsWindows())
DSM.DsmLoader.TryLoadCustomDSM(Logger);
_appIdentity = appId;
_legacyCallbackDelegate = LegacyCallbackHandler;
@@ -47,16 +46,15 @@ namespace NTwain
public ILogger Logger
{
get { return _logger = NullLogger.Instance; }
get { return _logger; }
set { _logger = value ?? NullLogger.Instance; }
}
internal IntPtr _hwnd;
internal TW_USERINTERFACE _userInterface; // kept around for disable to use
#if WINDOWS || NETFRAMEWORK
MessagePumpThread? _selfPump;
TW_EVENT _procEvent; // kept here so the alloc/free only happens once
#endif
// test threads a bit
//readonly BlockingCollection<MSG> _bgPendingMsgs = new();
SynchronizationContext? _pumpThreadMarshaller;
@@ -72,9 +70,8 @@ namespace NTwain
{
IsBackground = true
};
#if WINDOWS || NETFRAMEWORK
t.SetApartmentState(ApartmentState.STA); // just in case
#endif
if (OperatingSystem.IsWindows())
t.SetApartmentState(ApartmentState.STA); // just in case
t.Start();
}
@@ -105,9 +102,10 @@ namespace NTwain
_xferReady.Dispose();
//_bgPendingMsgs.CompleteAdding();
}
#if WINDOWS || NETFRAMEWORK
if (_procEvent.pEvent != IntPtr.Zero) Marshal.FreeHGlobal(_procEvent.pEvent);
#endif
if (OperatingSystem.IsWindows())
if (_procEvent.pEvent != IntPtr.Zero) Marshal.FreeHGlobal(_procEvent.pEvent);
disposedValue = true;
}
}
@@ -126,13 +124,15 @@ namespace NTwain
GC.SuppressFinalize(this);
}
#if WINDOWS || NETFRAMEWORK
/// <summary>
/// Loads and opens the TWAIN data source manager in a self-hosted message queue thread.
/// Loads and opens the TWAIN data source manager if you're using Windows.
/// Must close with <see cref="CloseDSMAsync"/>
/// if used.
/// </summary>
/// <returns></returns>
#if !NETFRAMEWORK
[SupportedOSPlatform("windows5.1.2600")]
#endif
public async Task<STS> OpenDSMAsync()
{
if (_selfPump == null)
@@ -154,6 +154,9 @@ namespace NTwain
/// </summary>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
#if !NETFRAMEWORK
[SupportedOSPlatform("windows5.1.2600")]
#endif
public async Task<STS> CloseDSMAsync()
{
if (_selfPump == null) throw new InvalidOperationException($"Cannot close if not opened with {nameof(OpenDSMAsync)}().");
@@ -165,10 +168,10 @@ namespace NTwain
}
return sts;
}
#endif
/// <summary>
/// Loads and opens the TWAIN data source manager.
/// If you're on windows you should NOT use this and instead use <see cref="OpenDSMAsync"/> and <see cref="CloseDSMAsync"/>.
/// </summary>
/// <param name="hwnd">Required if on Windows.</param>
/// <param name="uiThreadMarshaller">Context for TWAIN to invoke certain actions on the thread that the hwnd lives on.</param>
@@ -210,9 +213,7 @@ namespace NTwain
/// <exception cref="InvalidOperationException"></exception>
public STS CloseDSM()
{
#if WINDOWS || NETFRAMEWORK
if (_selfPump != null) throw new InvalidOperationException($"Cannot close if opened with {nameof(OpenDSMAsync)}().");
#endif
return CloseDSMReal();
}
@@ -329,12 +330,12 @@ namespace NTwain
CloseSource();
break;
case STATE.S3:
#if WINDOWS || NETFRAMEWORK
if (_selfPump != null)
{
try
{
_ = CloseDSMAsync();
if (OperatingSystem.IsWindowsVersionAtLeast(5, 1, 2600))
_ = CloseDSMAsync();
}
catch (InvalidOperationException) { }
}
@@ -342,9 +343,6 @@ namespace NTwain
{
CloseDSM();
}
#else
CloseDSM();
#endif
break;
case STATE.S2:
// can't really go lower