mirror of
https://github.com/soukoku/ntwain.git
synced 2026-01-09 11:21:06 +08:00
Initial attempt to remove winform/wpf.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,173 +1,171 @@
|
||||
#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.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
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.Run();
|
||||
_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 DummyForm : Form
|
||||
//{
|
||||
// public DummyForm()
|
||||
// {
|
||||
// ShowInTaskbar = false;
|
||||
// Width = 1;
|
||||
// Height = 1;
|
||||
// //WindowState = FormWindowState.Minimized;
|
||||
// Text = "NTwain Internal Loop";
|
||||
// }
|
||||
|
||||
// protected override void OnHandleCreated(EventArgs e)
|
||||
// {
|
||||
// base.OnHandleCreated(e);
|
||||
// }
|
||||
|
||||
// //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);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
336
src/NTwain/Native/Win32MessagePump.cs
Normal file
336
src/NTwain/Native/Win32MessagePump.cs
Normal file
@@ -0,0 +1,336 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.Graphics.Gdi;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
|
||||
namespace NTwain.Native;
|
||||
|
||||
#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();
|
||||
|
||||
public Win32MessagePump()
|
||||
{
|
||||
_hInstance = PInvoke.GetModuleHandle((string?)null);
|
||||
_threadId = PInvoke.GetCurrentThreadId();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the main (hidden) message window handle.
|
||||
/// </summary>
|
||||
public HWND MainWindow => _mainWindow;
|
||||
|
||||
public bool InvokeRequired => PInvoke.GetCurrentThreadId() != _threadId;
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
int exitCode = RunMessageLoop();
|
||||
|
||||
Dispose();
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
private bool RegisterWindowClass()
|
||||
{
|
||||
s_wndProc = WindowProc;
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (char* className = WindowClassName)
|
||||
{
|
||||
var wc = new WNDCLASSEXW
|
||||
{
|
||||
cbSize = (uint)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)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Message filter exception: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Work item exception: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
/// 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
|
||||
};
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"$schema": "https://aka.ms/CsWin32.schema.json",
|
||||
"allowMarshaling": false
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -53,10 +51,9 @@ namespace NTwain
|
||||
|
||||
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;
|
||||
@@ -126,13 +123,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 +153,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 +167,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 +212,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,7 +329,6 @@ namespace NTwain
|
||||
CloseSource();
|
||||
break;
|
||||
case STATE.S3:
|
||||
#if WINDOWS || NETFRAMEWORK
|
||||
if (_selfPump != null)
|
||||
{
|
||||
try
|
||||
@@ -342,9 +341,6 @@ namespace NTwain
|
||||
{
|
||||
CloseDSM();
|
||||
}
|
||||
#else
|
||||
CloseDSM();
|
||||
#endif
|
||||
break;
|
||||
case STATE.S2:
|
||||
// can't really go lower
|
||||
|
||||
Reference in New Issue
Block a user