From 1364150697983f881268e84fb104173db445940e Mon Sep 17 00:00:00 2001
From: Eugene Wang <8755753+soukoku@users.noreply.github.com>
Date: Sat, 15 Apr 2023 06:33:12 -0400
Subject: [PATCH] Integrated message thread inside TwainAppSession as a
different OpenDSM method.
---
NTwain.sln | 24 ++++++++-
samples/WinConsole32/Program.cs | 3 +-
samples/WinForm32/Form1.cs | 24 +++------
src/NTwain/Data/TWAINH_EXTRAS.cs | 2 +-
src/NTwain/MessagePumpThread.cs | 40 ++++++++++----
src/NTwain/NTwain.csproj | 4 +-
src/NTwain/TwainAppSession.Windows.cs | 2 +-
src/NTwain/TwainAppSession.cs | 77 ++++++++++++++++++++++++++-
8 files changed, 142 insertions(+), 34 deletions(-)
diff --git a/NTwain.sln b/NTwain.sln
index 544e7f9..762ecc0 100644
--- a/NTwain.sln
+++ b/NTwain.sln
@@ -28,74 +28,96 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinForm64", "samples\WinFor
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "csizes", "csizes\csizes.vcxproj", "{1AABD2DC-3F81-4301-938B-3EC2EDEF38D4}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinConsole32", "samples\WinConsole32\WinConsole32.csproj", "{4E2417E7-FDC3-46D7-B976-84A97B500B74}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinConsole32", "samples\WinConsole32\WinConsole32.csproj", "{4E2417E7-FDC3-46D7-B976-84A97B500B74}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3C8A3CF9-A60D-4F21-B866-D291A7AABD4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3C8A3CF9-A60D-4F21-B866-D291A7AABD4A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3C8A3CF9-A60D-4F21-B866-D291A7AABD4A}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {3C8A3CF9-A60D-4F21-B866-D291A7AABD4A}.Debug|ARM64.Build.0 = Debug|Any CPU
{3C8A3CF9-A60D-4F21-B866-D291A7AABD4A}.Debug|x64.ActiveCfg = Debug|Any CPU
{3C8A3CF9-A60D-4F21-B866-D291A7AABD4A}.Debug|x64.Build.0 = Debug|Any CPU
{3C8A3CF9-A60D-4F21-B866-D291A7AABD4A}.Debug|x86.ActiveCfg = Debug|Any CPU
{3C8A3CF9-A60D-4F21-B866-D291A7AABD4A}.Debug|x86.Build.0 = Debug|Any CPU
{3C8A3CF9-A60D-4F21-B866-D291A7AABD4A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3C8A3CF9-A60D-4F21-B866-D291A7AABD4A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3C8A3CF9-A60D-4F21-B866-D291A7AABD4A}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {3C8A3CF9-A60D-4F21-B866-D291A7AABD4A}.Release|ARM64.Build.0 = Release|Any CPU
{3C8A3CF9-A60D-4F21-B866-D291A7AABD4A}.Release|x64.ActiveCfg = Release|Any CPU
{3C8A3CF9-A60D-4F21-B866-D291A7AABD4A}.Release|x64.Build.0 = Release|Any CPU
{3C8A3CF9-A60D-4F21-B866-D291A7AABD4A}.Release|x86.ActiveCfg = Release|Any CPU
{3C8A3CF9-A60D-4F21-B866-D291A7AABD4A}.Release|x86.Build.0 = Release|Any CPU
{7792A94E-D0B4-440D-8BD5-CA1CA548782C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7792A94E-D0B4-440D-8BD5-CA1CA548782C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7792A94E-D0B4-440D-8BD5-CA1CA548782C}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {7792A94E-D0B4-440D-8BD5-CA1CA548782C}.Debug|ARM64.Build.0 = Debug|Any CPU
{7792A94E-D0B4-440D-8BD5-CA1CA548782C}.Debug|x64.ActiveCfg = Debug|Any CPU
{7792A94E-D0B4-440D-8BD5-CA1CA548782C}.Debug|x64.Build.0 = Debug|Any CPU
{7792A94E-D0B4-440D-8BD5-CA1CA548782C}.Debug|x86.ActiveCfg = Debug|Any CPU
{7792A94E-D0B4-440D-8BD5-CA1CA548782C}.Debug|x86.Build.0 = Debug|Any CPU
{7792A94E-D0B4-440D-8BD5-CA1CA548782C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7792A94E-D0B4-440D-8BD5-CA1CA548782C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7792A94E-D0B4-440D-8BD5-CA1CA548782C}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {7792A94E-D0B4-440D-8BD5-CA1CA548782C}.Release|ARM64.Build.0 = Release|Any CPU
{7792A94E-D0B4-440D-8BD5-CA1CA548782C}.Release|x64.ActiveCfg = Release|Any CPU
{7792A94E-D0B4-440D-8BD5-CA1CA548782C}.Release|x64.Build.0 = Release|Any CPU
{7792A94E-D0B4-440D-8BD5-CA1CA548782C}.Release|x86.ActiveCfg = Release|Any CPU
{7792A94E-D0B4-440D-8BD5-CA1CA548782C}.Release|x86.Build.0 = Release|Any CPU
{C9666CB2-C9A6-48C8-AB51-D616A48058A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C9666CB2-C9A6-48C8-AB51-D616A48058A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C9666CB2-C9A6-48C8-AB51-D616A48058A7}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {C9666CB2-C9A6-48C8-AB51-D616A48058A7}.Debug|ARM64.Build.0 = Debug|Any CPU
{C9666CB2-C9A6-48C8-AB51-D616A48058A7}.Debug|x64.ActiveCfg = Debug|Any CPU
{C9666CB2-C9A6-48C8-AB51-D616A48058A7}.Debug|x64.Build.0 = Debug|Any CPU
{C9666CB2-C9A6-48C8-AB51-D616A48058A7}.Debug|x86.ActiveCfg = Debug|Any CPU
{C9666CB2-C9A6-48C8-AB51-D616A48058A7}.Debug|x86.Build.0 = Debug|Any CPU
{C9666CB2-C9A6-48C8-AB51-D616A48058A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C9666CB2-C9A6-48C8-AB51-D616A48058A7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C9666CB2-C9A6-48C8-AB51-D616A48058A7}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {C9666CB2-C9A6-48C8-AB51-D616A48058A7}.Release|ARM64.Build.0 = Release|Any CPU
{C9666CB2-C9A6-48C8-AB51-D616A48058A7}.Release|x64.ActiveCfg = Release|Any CPU
{C9666CB2-C9A6-48C8-AB51-D616A48058A7}.Release|x64.Build.0 = Release|Any CPU
{C9666CB2-C9A6-48C8-AB51-D616A48058A7}.Release|x86.ActiveCfg = Release|Any CPU
{C9666CB2-C9A6-48C8-AB51-D616A48058A7}.Release|x86.Build.0 = Release|Any CPU
{1AABD2DC-3F81-4301-938B-3EC2EDEF38D4}.Debug|Any CPU.ActiveCfg = Debug|x64
{1AABD2DC-3F81-4301-938B-3EC2EDEF38D4}.Debug|Any CPU.Build.0 = Debug|x64
+ {1AABD2DC-3F81-4301-938B-3EC2EDEF38D4}.Debug|ARM64.ActiveCfg = Debug|x64
+ {1AABD2DC-3F81-4301-938B-3EC2EDEF38D4}.Debug|ARM64.Build.0 = Debug|x64
{1AABD2DC-3F81-4301-938B-3EC2EDEF38D4}.Debug|x64.ActiveCfg = Debug|x64
{1AABD2DC-3F81-4301-938B-3EC2EDEF38D4}.Debug|x64.Build.0 = Debug|x64
{1AABD2DC-3F81-4301-938B-3EC2EDEF38D4}.Debug|x86.ActiveCfg = Debug|Win32
{1AABD2DC-3F81-4301-938B-3EC2EDEF38D4}.Debug|x86.Build.0 = Debug|Win32
{1AABD2DC-3F81-4301-938B-3EC2EDEF38D4}.Release|Any CPU.ActiveCfg = Release|x64
{1AABD2DC-3F81-4301-938B-3EC2EDEF38D4}.Release|Any CPU.Build.0 = Release|x64
+ {1AABD2DC-3F81-4301-938B-3EC2EDEF38D4}.Release|ARM64.ActiveCfg = Release|x64
+ {1AABD2DC-3F81-4301-938B-3EC2EDEF38D4}.Release|ARM64.Build.0 = Release|x64
{1AABD2DC-3F81-4301-938B-3EC2EDEF38D4}.Release|x64.ActiveCfg = Release|x64
{1AABD2DC-3F81-4301-938B-3EC2EDEF38D4}.Release|x64.Build.0 = Release|x64
{1AABD2DC-3F81-4301-938B-3EC2EDEF38D4}.Release|x86.ActiveCfg = Release|Win32
{1AABD2DC-3F81-4301-938B-3EC2EDEF38D4}.Release|x86.Build.0 = Release|Win32
{4E2417E7-FDC3-46D7-B976-84A97B500B74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4E2417E7-FDC3-46D7-B976-84A97B500B74}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4E2417E7-FDC3-46D7-B976-84A97B500B74}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {4E2417E7-FDC3-46D7-B976-84A97B500B74}.Debug|ARM64.Build.0 = Debug|Any CPU
{4E2417E7-FDC3-46D7-B976-84A97B500B74}.Debug|x64.ActiveCfg = Debug|Any CPU
{4E2417E7-FDC3-46D7-B976-84A97B500B74}.Debug|x64.Build.0 = Debug|Any CPU
{4E2417E7-FDC3-46D7-B976-84A97B500B74}.Debug|x86.ActiveCfg = Debug|Any CPU
{4E2417E7-FDC3-46D7-B976-84A97B500B74}.Debug|x86.Build.0 = Debug|Any CPU
{4E2417E7-FDC3-46D7-B976-84A97B500B74}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4E2417E7-FDC3-46D7-B976-84A97B500B74}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4E2417E7-FDC3-46D7-B976-84A97B500B74}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {4E2417E7-FDC3-46D7-B976-84A97B500B74}.Release|ARM64.Build.0 = Release|Any CPU
{4E2417E7-FDC3-46D7-B976-84A97B500B74}.Release|x64.ActiveCfg = Release|Any CPU
{4E2417E7-FDC3-46D7-B976-84A97B500B74}.Release|x64.Build.0 = Release|Any CPU
{4E2417E7-FDC3-46D7-B976-84A97B500B74}.Release|x86.ActiveCfg = Release|Any CPU
diff --git a/samples/WinConsole32/Program.cs b/samples/WinConsole32/Program.cs
index 7a2892a..d985ab8 100644
--- a/samples/WinConsole32/Program.cs
+++ b/samples/WinConsole32/Program.cs
@@ -11,14 +11,13 @@ namespace WinConsole32
var libVer = FileVersionInfo.GetVersionInfo(typeof(TwainAppSession).Assembly.Location).ProductVersion;
Console.WriteLine($"Console sample {(TWPlatform.Is32bit ? " 32bit" : " 64bit")} on NTwain {libVer}");
- MessagePumpThread pump = new MessagePumpThread();
TwainAppSession session = new TwainAppSession(Environment.ProcessPath!);
session.StateChanged += Session_StateChanged;
session.SourceDisabled += Session_SourceDisabled1;
session.Transferred += Session_Transferred;
- var sts = await pump.AttachAsync(session);
+ var sts = await session.OpenDSMAsync();
if (sts.IsSuccess)
{
diff --git a/samples/WinForm32/Form1.cs b/samples/WinForm32/Form1.cs
index 40017d7..dd78657 100644
--- a/samples/WinForm32/Form1.cs
+++ b/samples/WinForm32/Form1.cs
@@ -19,7 +19,6 @@ namespace WinFormSample
public partial class Form1 : Form
{
bool useDiyPump = true;
- MessagePumpThread pump;
TwainAppSession twain;
readonly string saveFolder;
readonly Stopwatch watch = new();
@@ -36,7 +35,6 @@ namespace WinFormSample
var libVer = FileVersionInfo.GetVersionInfo(typeof(TwainAppSession).Assembly.Location).ProductVersion;
Text += $"{(TWPlatform.Is32bit ? " 32bit" : " 64bit")} on NTwain {libVer}";
- pump = new MessagePumpThread();
TWPlatform.PreferLegacyDSM = false;
twain = new TwainAppSession(Assembly.GetExecutingAssembly().Location);
@@ -87,35 +85,29 @@ namespace WinFormSample
}
}
- protected override void OnHandleCreated(EventArgs e)
+ protected override async void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if (useDiyPump)
{
- _ = pump.AttachAsync(twain);
+ var sts = await twain.OpenDSMAsync();
+ Debug.WriteLine($"OpenDSMAsync={sts}");
}
else
{
var hwnd = this.Handle;
- var rc = twain.OpenDSM(hwnd, SynchronizationContext.Current!);
+ var sts = twain.OpenDSM(hwnd, SynchronizationContext.Current!);
twain.AddWinformFilter();
- Debug.WriteLine($"OpenDSM={rc}");
+ Debug.WriteLine($"OpenDSM={sts}");
}
}
protected override void OnClosing(CancelEventArgs e)
{
- if (useDiyPump)
- {
- pump.Detatch();
- }
- else
- {
- var finalState = twain.TryStepdown(STATE.S2);
- Debug.WriteLine($"Stepdown result state={finalState}");
- twain.RemoveWinformFilter();
- }
+ var finalState = twain.TryStepdown(STATE.S2);
+ Debug.WriteLine($"Stepdown result state={finalState}");
+ twain.RemoveWinformFilter();
base.OnClosing(e);
}
diff --git a/src/NTwain/Data/TWAINH_EXTRAS.cs b/src/NTwain/Data/TWAINH_EXTRAS.cs
index eecdd7f..ddc7304 100644
--- a/src/NTwain/Data/TWAINH_EXTRAS.cs
+++ b/src/NTwain/Data/TWAINH_EXTRAS.cs
@@ -1017,7 +1017,7 @@ namespace NTwain.Data
/// For pointers you'd read it yourself with
/// .
/// Unless it's a handle () to non-twain-strings, then you'd use
- /// .
+ /// .
///
///
///
diff --git a/src/NTwain/MessagePumpThread.cs b/src/NTwain/MessagePumpThread.cs
index f9145e2..1bdba63 100644
--- a/src/NTwain/MessagePumpThread.cs
+++ b/src/NTwain/MessagePumpThread.cs
@@ -12,7 +12,7 @@ namespace NTwain
/// For use under Windows to host a message pump in non-winform/wpf apps.
/// This is highly experimental.
///
- public class MessagePumpThread
+ class MessagePumpThread
{
DummyForm? _dummyForm;
TwainAppSession? _twain;
@@ -70,11 +70,32 @@ namespace NTwain
///
/// Detatches a previously attached session and stops the thread.
///
- public void Detatch()
+ public async Task DetatchAsync()
{
+ STS sts = default;
if (_dummyForm != null && _twain != null)
{
- _dummyForm.BeginInvoke(_dummyForm.Close);
+ TaskCompletionSource tcs = new();
+ _dummyForm.BeginInvoke(() =>
+ {
+ sts = _twain.CloseDSMReal();
+ if (sts.IsSuccess)
+ {
+ _twain.RemoveWinformFilter();
+ _dummyForm.Close();
+ _twain = null;
+ }
+ });
+ await tcs.Task;
+ }
+ return sts;
+ }
+
+ public void BringWindowToFront()
+ {
+ if (_dummyForm != null)
+ {
+ _dummyForm.BeginInvoke(_dummyForm.BringToFront);
}
}
@@ -84,13 +105,6 @@ namespace NTwain
_dummyForm = new DummyForm();
_dummyForm.FormClosed += (s, e) =>
{
- if (_twain != null)
- {
- _twain.TryStepdown(STATE.S4);
- _twain.RemoveWinformFilter();
- _twain.CloseDSM();
- _twain = null;
- }
_dummyForm = null;
};
Application.Run(_dummyForm);
@@ -104,6 +118,12 @@ namespace NTwain
ShowInTaskbar = false;
WindowState = FormWindowState.Minimized;
}
+
+ protected override void OnShown(EventArgs e)
+ {
+ BringToFront();
+ base.OnShown(e);
+ }
}
}
}
diff --git a/src/NTwain/NTwain.csproj b/src/NTwain/NTwain.csproj
index 0dcfd99..ac8049e 100644
--- a/src/NTwain/NTwain.csproj
+++ b/src/NTwain/NTwain.csproj
@@ -21,11 +21,11 @@
-
+
-
+
PreserveNewest
diff --git a/src/NTwain/TwainAppSession.Windows.cs b/src/NTwain/TwainAppSession.Windows.cs
index f818c36..cb5584e 100644
--- a/src/NTwain/TwainAppSession.Windows.cs
+++ b/src/NTwain/TwainAppSession.Windows.cs
@@ -35,7 +35,7 @@ namespace NTwain
///
/// Registers this session for use in a WPF UI thread.
- /// This requires the hwnd used in
+ /// This requires the hwnd used in
/// be a valid WPF window handle.
///
public void AddWpfHook()
diff --git a/src/NTwain/TwainAppSession.cs b/src/NTwain/TwainAppSession.cs
index b360a98..1eeeaeb 100644
--- a/src/NTwain/TwainAppSession.cs
+++ b/src/NTwain/TwainAppSession.cs
@@ -6,6 +6,7 @@ using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
+using System.Threading.Tasks;
using System.Xml;
namespace NTwain
@@ -99,6 +100,7 @@ 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
@@ -146,7 +148,6 @@ namespace NTwain
{
// this will end the bg thread
_xferReady.Dispose();
-
//_bgPendingMsgs.CompleteAdding();
}
#if WINDOWS || NETFRAMEWORK
@@ -170,6 +171,45 @@ namespace NTwain
GC.SuppressFinalize(this);
}
+#if WINDOWS || NETFRAMEWORK
+ ///
+ /// Loads and opens the TWAIN data source manager in a self-hosted message queue thread.
+ /// Highly experimental and only use if necessary. Must close with
+ /// if used.
+ ///
+ ///
+ public async Task OpenDSMAsync()
+ {
+ if (_selfPump == null)
+ {
+ var pump = new MessagePumpThread();
+ var sts = await pump.AttachAsync(this);
+ if (sts.IsSuccess)
+ {
+ _selfPump = pump;
+ }
+ return sts;
+ }
+ return new STS { RC = TWRC.FAILURE, STATUS = new TW_STATUS { ConditionCode = TWCC.SEQERROR } };
+ }
+
+ ///
+ /// Closes the TWAIN data source manager if opened with .
+ ///
+ ///
+ ///
+ public async Task CloseDSMAsync()
+ {
+ if (_selfPump == null) throw new InvalidOperationException($"Cannot close if not opened with {nameof(OpenDSMAsync)}().");
+
+ var sts = await _selfPump.DetatchAsync();
+ if (sts.IsSuccess)
+ {
+ _selfPump = null;
+ }
+ return sts;
+ }
+#endif
///
/// Loads and opens the TWAIN data source manager.
@@ -210,8 +250,21 @@ namespace NTwain
/// Closes the TWAIN data source manager.
///
///
+ ///
public STS CloseDSM()
{
+#if WINDOWS || NETFRAMEWORK
+ if (_selfPump != null) throw new InvalidOperationException($"Cannot close if opened with {nameof(OpenDSMAsync)}().");
+#endif
+ return CloseDSMReal();
+ }
+
+ ///
+ /// Closes the TWAIN data source manager.
+ ///
+ ///
+ internal STS CloseDSMReal()
+ {
var rc = DGControl.Parent.CloseDSM(ref _appIdentity, _hwnd);
if (rc == TWRC.SUCCESS)
{
@@ -312,7 +365,29 @@ namespace NTwain
CloseSource();
break;
case STATE.S3:
+#if WINDOWS || NETFRAMEWORK
+ if (_selfPump != null)
+ {
+ try
+ {
+ _ = CloseDSMAsync();
+ }
+ catch (InvalidOperationException) { }
+ }
+ else
+ {
+ CloseDSM();
+ }
+#else
CloseDSM();
+#endif
+ break;
+ case STATE.S2:
+ // can't really go lower
+ if (targetState < STATE.S2)
+ {
+ return State;
+ }
break;
}
if (oldState == State)