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)