Merge pull request #44 from soukoku/internal-thread-test

Integrated message thread inside TwainAppSession
This commit is contained in:
Eugene Wang 2023-04-15 06:34:21 -04:00 committed by GitHub
commit ba76de99c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 142 additions and 34 deletions

View File

@ -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

View File

@ -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)
{

View File

@ -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);
}

View File

@ -1017,7 +1017,7 @@ namespace NTwain.Data
/// For pointers you'd read it yourself with
/// <see cref="ValueReader.ReadTWTYData{TValue}(IntPtr, TWTY, int)"/>.
/// Unless it's a handle (<see cref="TWTY.HANDLE"/>) to non-twain-strings, then you'd use
/// <see cref="ReadHandleString(IMemoryManager, int)"/>.
/// <see cref="ReadHandleString"/>.
/// </summary>
/// <typeparam name="TValue"></typeparam>
/// <returns></returns>

View File

@ -12,7 +12,7 @@ namespace NTwain
/// For use under Windows to host a message pump in non-winform/wpf apps.
/// This is highly experimental.
/// </summary>
public class MessagePumpThread
class MessagePumpThread
{
DummyForm? _dummyForm;
TwainAppSession? _twain;
@ -70,11 +70,32 @@ namespace NTwain
/// <summary>
/// Detatches a previously attached session and stops the thread.
/// </summary>
public void Detatch()
public async Task<STS> DetatchAsync()
{
STS sts = default;
if (_dummyForm != null && _twain != null)
{
_dummyForm.BeginInvoke(_dummyForm.Close);
TaskCompletionSource<bool> 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);
}
}
}
}

View File

@ -21,11 +21,11 @@
<ItemGroup Condition=" '$(TargetFramework)' != 'net462'">
<PackageReference Include="System.Text.Encoding.CodePages" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<ItemGroup Condition="'$(Configuration)'!='Release'">
<None Remove="runtimes\win-x64\native\TWAINDSM.dll" />
<None Remove="runtimes\win-x86\native\TWAINDSM.dll" />
</ItemGroup>
<ItemGroup>
<ItemGroup Condition="'$(Configuration)'!='Release'">
<Content Include="runtimes\win-x64\native\TWAINDSM.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

View File

@ -35,7 +35,7 @@ namespace NTwain
/// <summary>
/// Registers this session for use in a WPF UI thread.
/// This requires the hwnd used in <see cref="OpenDSM(IntPtr)"/>
/// This requires the hwnd used in <see cref="OpenDSM"/>
/// be a valid WPF window handle.
/// </summary>
public void AddWpfHook()

View File

@ -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
/// <summary>
/// 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 <see cref="CloseDSMAsync"/>
/// if used.
/// </summary>
/// <returns></returns>
public async Task<STS> 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 } };
}
/// <summary>
/// Closes the TWAIN data source manager if opened with <see cref="OpenDSMAsync"/>.
/// </summary>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
public async Task<STS> 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
/// <summary>
/// Loads and opens the TWAIN data source manager.
@ -210,8 +250,21 @@ namespace NTwain
/// Closes the TWAIN data source manager.
/// </summary>
/// <returns></returns>
/// <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();
}
/// <summary>
/// Closes the TWAIN data source manager.
/// </summary>
/// <returns></returns>
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)