mirror of
https://github.com/soukoku/ntwain.git
synced 2025-07-17 00:07:47 +08:00
Experimenting with bg thread processing.
This commit is contained in:
parent
84251a6a5e
commit
679a00818a
@ -14,7 +14,7 @@ namespace SampleConsole
|
|||||||
{
|
{
|
||||||
TwainPlatform.PreferLegacyDSM = true;
|
TwainPlatform.PreferLegacyDSM = true;
|
||||||
|
|
||||||
var twain = new TwainSession(Assembly.GetExecutingAssembly().Location);
|
var twain = new TwainSession(new InPlaceMarshaller(), Assembly.GetExecutingAssembly().Location);
|
||||||
twain.StateChanged += Twain_StateChanged;
|
twain.StateChanged += Twain_StateChanged;
|
||||||
|
|
||||||
var hwnd = IntPtr.Zero; // required for windows
|
var hwnd = IntPtr.Zero; // required for windows
|
||||||
|
@ -18,10 +18,17 @@ namespace WinForm32
|
|||||||
Text += TwainPlatform.Is32bit ? " 32bit" : " 64bit";
|
Text += TwainPlatform.Is32bit ? " 32bit" : " 64bit";
|
||||||
TwainPlatform.PreferLegacyDSM = false;
|
TwainPlatform.PreferLegacyDSM = false;
|
||||||
|
|
||||||
twain = new TwainSession(Assembly.GetExecutingAssembly().Location);
|
twain = new TwainSession(new WinformMarshaller(this), Assembly.GetExecutingAssembly().Location);
|
||||||
twain.StateChanged += Twain_StateChanged;
|
twain.StateChanged += Twain_StateChanged;
|
||||||
twain.DefaultSourceChanged += Twain_DefaultSourceChanged;
|
twain.DefaultSourceChanged += Twain_DefaultSourceChanged;
|
||||||
twain.CurrentSourceChanged += Twain_CurrentSourceChanged;
|
twain.CurrentSourceChanged += Twain_CurrentSourceChanged;
|
||||||
|
|
||||||
|
this.Disposed += Form1_Disposed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Form1_Disposed(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
twain.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Twain_CurrentSourceChanged(TwainSession arg1, TW_IDENTITY_LEGACY ds)
|
private void Twain_CurrentSourceChanged(TwainSession arg1, TW_IDENTITY_LEGACY ds)
|
||||||
|
49
src/NTwain/ThreadMarshaller.Windows.cs
Normal file
49
src/NTwain/ThreadMarshaller.Windows.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#if WINDOWS || NETFRAMEWORK
|
||||||
|
using System;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
|
||||||
|
namespace NTwain
|
||||||
|
{
|
||||||
|
|
||||||
|
public class WinformMarshaller : IThreadMarshaller
|
||||||
|
{
|
||||||
|
private readonly Control control;
|
||||||
|
|
||||||
|
public WinformMarshaller(System.Windows.Forms.Control control)
|
||||||
|
{
|
||||||
|
this.control = control;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BeginInvoke(Delegate work, params object[] args)
|
||||||
|
{
|
||||||
|
control.BeginInvoke(work, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? Invoke(Delegate work, params object[] args)
|
||||||
|
{
|
||||||
|
return control.Invoke(work, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WpfMarshaller : IThreadMarshaller
|
||||||
|
{
|
||||||
|
private readonly Dispatcher dispatcher;
|
||||||
|
|
||||||
|
public WpfMarshaller(Dispatcher dispatcher)
|
||||||
|
{
|
||||||
|
this.dispatcher = dispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BeginInvoke(Delegate work, params object[] args)
|
||||||
|
{
|
||||||
|
dispatcher.BeginInvoke(work, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? Invoke(Delegate work, params object[] args)
|
||||||
|
{
|
||||||
|
return dispatcher.Invoke(work, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
46
src/NTwain/ThreadMarshaller.cs
Normal file
46
src/NTwain/ThreadMarshaller.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace NTwain
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Allows work to be marshalled to a different (usually UI) thread as necessary.
|
||||||
|
/// </summary>
|
||||||
|
public interface IThreadMarshaller
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Starts work asynchronously and returns immediately.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="work"></param>
|
||||||
|
/// <param name="args"></param>
|
||||||
|
void BeginInvoke(Delegate work, params object[] args);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts work synchronously until it returns.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="work"></param>
|
||||||
|
/// <param name="args"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
object? Invoke(Delegate work, params object[] args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// No marshalling occurs. Invokes happen right in place.
|
||||||
|
/// </summary>
|
||||||
|
public class InPlaceMarshaller : IThreadMarshaller
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No async invocation here.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="work"></param>
|
||||||
|
/// <param name="args"></param>
|
||||||
|
public void BeginInvoke(Delegate work, params object[] args)
|
||||||
|
{
|
||||||
|
work.DynamicInvoke(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? Invoke(Delegate work, params object[] args)
|
||||||
|
{
|
||||||
|
return work.DynamicInvoke(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -90,19 +90,13 @@ namespace NTwain
|
|||||||
switch (msg)
|
switch (msg)
|
||||||
{
|
{
|
||||||
case MSG.XFERREADY:
|
case MSG.XFERREADY:
|
||||||
break;
|
|
||||||
case MSG.DEVICEEVENT:
|
case MSG.DEVICEEVENT:
|
||||||
// use diff thread to get it
|
|
||||||
//if (DGControl.DeviceEvent.Get(ref _appIdentity, ref _currentDS, out TW_DEVICEEVENT de) == STS.SUCCESS)
|
|
||||||
//{
|
|
||||||
|
|
||||||
//}
|
|
||||||
break;
|
|
||||||
case MSG.CLOSEDSOK:
|
case MSG.CLOSEDSOK:
|
||||||
DisableSource();
|
|
||||||
break;
|
|
||||||
case MSG.CLOSEDSREQ:
|
case MSG.CLOSEDSREQ:
|
||||||
DisableSource();
|
// the reason we post these to the background is
|
||||||
|
// if they're coming from UI message loop then
|
||||||
|
// this needs to return asap
|
||||||
|
_bgPendingMsgs.Add(msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,5 +130,10 @@ namespace NTwain
|
|||||||
/// Fires when <see cref="CurrentSource"/> changes.
|
/// Fires when <see cref="CurrentSource"/> changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<TwainSession, TW_IDENTITY_LEGACY>? CurrentSourceChanged;
|
public event Action<TwainSession, TW_IDENTITY_LEGACY>? CurrentSourceChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fires when the source has some device event happening.
|
||||||
|
/// </summary>
|
||||||
|
public event Action<TwainSession, TW_DEVICEEVENT>? DeviceEvent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
14
src/NTwain/TwainSession.Xfers.cs
Normal file
14
src/NTwain/TwainSession.Xfers.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using NTwain.Data;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace NTwain
|
||||||
|
{
|
||||||
|
// this file contains various xfer methods
|
||||||
|
|
||||||
|
partial class TwainSession
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -1,37 +1,43 @@
|
|||||||
using NTwain.Data;
|
using NTwain.Data;
|
||||||
using NTwain.Triplets;
|
using NTwain.Triplets;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace NTwain
|
namespace NTwain
|
||||||
{
|
{
|
||||||
// this file contains initialization/cleanup things.
|
// this file contains initialization/cleanup things.
|
||||||
|
|
||||||
public partial class TwainSession
|
public partial class TwainSession : IDisposable
|
||||||
{
|
{
|
||||||
static bool __encodingRegistered;
|
static bool __encodingRegistered;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates TWAIN session with app info derived an executable file.
|
/// Creates TWAIN session with app info derived an executable file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="uiThreadMarshaller"></param>
|
||||||
/// <param name="exeFilePath"></param>
|
/// <param name="exeFilePath"></param>
|
||||||
/// <param name="appLanguage"></param>
|
/// <param name="appLanguage"></param>
|
||||||
/// <param name="appCountry"></param>
|
/// <param name="appCountry"></param>
|
||||||
public TwainSession(string exeFilePath,
|
public TwainSession(IThreadMarshaller uiThreadMarshaller,
|
||||||
TWLG appLanguage = TWLG.ENGLISH_USA, TWCY appCountry = TWCY.USA) :
|
string exeFilePath,
|
||||||
this(FileVersionInfo.GetVersionInfo(exeFilePath),
|
TWLG appLanguage = TWLG.ENGLISH_USA, TWCY appCountry = TWCY.USA) :
|
||||||
appLanguage, appCountry)
|
this(uiThreadMarshaller, FileVersionInfo.GetVersionInfo(exeFilePath), appLanguage, appCountry)
|
||||||
{ }
|
{ }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates TWAIN session with app info derived from a <see cref="FileVersionInfo"/> object.
|
/// Creates TWAIN session with app info derived from a <see cref="FileVersionInfo"/> object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="uiThreadMarshaller"></param>
|
||||||
/// <param name="appInfo"></param>
|
/// <param name="appInfo"></param>
|
||||||
/// <param name="appLanguage"></param>
|
/// <param name="appLanguage"></param>
|
||||||
/// <param name="appCountry"></param>
|
/// <param name="appCountry"></param>
|
||||||
public TwainSession(FileVersionInfo appInfo,
|
public TwainSession(IThreadMarshaller uiThreadMarshaller,
|
||||||
|
FileVersionInfo appInfo,
|
||||||
TWLG appLanguage = TWLG.ENGLISH_USA, TWCY appCountry = TWCY.USA) :
|
TWLG appLanguage = TWLG.ENGLISH_USA, TWCY appCountry = TWCY.USA) :
|
||||||
this(appInfo.CompanyName ?? "",
|
this(uiThreadMarshaller,
|
||||||
|
appInfo.CompanyName ?? "",
|
||||||
appInfo.ProductName ?? "",
|
appInfo.ProductName ?? "",
|
||||||
appInfo.ProductName ?? "",
|
appInfo.ProductName ?? "",
|
||||||
new Version(appInfo.FileVersion ?? "1.0"),
|
new Version(appInfo.FileVersion ?? "1.0"),
|
||||||
@ -40,6 +46,7 @@ namespace NTwain
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates TWAIN session with explicit app info.
|
/// Creates TWAIN session with explicit app info.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="uiThreadMarshaller"></param>
|
||||||
/// <param name="companyName"></param>
|
/// <param name="companyName"></param>
|
||||||
/// <param name="productFamily"></param>
|
/// <param name="productFamily"></param>
|
||||||
/// <param name="productName"></param>
|
/// <param name="productName"></param>
|
||||||
@ -48,11 +55,13 @@ namespace NTwain
|
|||||||
/// <param name="appLanguage"></param>
|
/// <param name="appLanguage"></param>
|
||||||
/// <param name="appCountry"></param>
|
/// <param name="appCountry"></param>
|
||||||
/// <param name="supportedTypes"></param>
|
/// <param name="supportedTypes"></param>
|
||||||
public TwainSession(string companyName, string productFamily, string productName,
|
public TwainSession(IThreadMarshaller uiThreadMarshaller,
|
||||||
|
string companyName, string productFamily, string productName,
|
||||||
Version productVersion, string productDescription = "",
|
Version productVersion, string productDescription = "",
|
||||||
TWLG appLanguage = TWLG.ENGLISH_USA, TWCY appCountry = TWCY.USA,
|
TWLG appLanguage = TWLG.ENGLISH_USA, TWCY appCountry = TWCY.USA,
|
||||||
DG supportedTypes = DG.IMAGE)
|
DG supportedTypes = DG.IMAGE)
|
||||||
{
|
{
|
||||||
|
// todo: find a better place for this
|
||||||
if (!__encodingRegistered)
|
if (!__encodingRegistered)
|
||||||
{
|
{
|
||||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||||
@ -79,10 +88,86 @@ namespace NTwain
|
|||||||
|
|
||||||
_legacyCallbackDelegate = LegacyCallbackHandler;
|
_legacyCallbackDelegate = LegacyCallbackHandler;
|
||||||
_osxCallbackDelegate = OSXCallbackHandler;
|
_osxCallbackDelegate = OSXCallbackHandler;
|
||||||
|
|
||||||
|
_uiThreadMarshaller = uiThreadMarshaller;
|
||||||
|
StartBackgroundThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal IntPtr _hwnd;
|
internal IntPtr _hwnd;
|
||||||
internal TW_USERINTERFACE _userInterface;
|
internal TW_USERINTERFACE _userInterface;
|
||||||
|
// test this a bit
|
||||||
|
readonly BlockingCollection<MSG> _bgPendingMsgs = new();
|
||||||
|
private readonly IThreadMarshaller _uiThreadMarshaller;
|
||||||
|
private bool disposedValue;
|
||||||
|
|
||||||
|
void StartBackgroundThread()
|
||||||
|
{
|
||||||
|
Thread t = new(BackgroundThreadLoop)
|
||||||
|
{
|
||||||
|
IsBackground = true
|
||||||
|
};
|
||||||
|
t.SetApartmentState(ApartmentState.STA); // just in case
|
||||||
|
t.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BackgroundThreadLoop(object? obj)
|
||||||
|
{
|
||||||
|
foreach (var msg in _bgPendingMsgs.GetConsumingEnumerable())
|
||||||
|
{
|
||||||
|
switch (msg)
|
||||||
|
{
|
||||||
|
case MSG.CLOSEDS:
|
||||||
|
case MSG.CLOSEDSREQ:
|
||||||
|
// this should be done on ui thread (or same one that enabled the ds)
|
||||||
|
_uiThreadMarshaller.BeginInvoke(() =>
|
||||||
|
{
|
||||||
|
DisableSource();
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case MSG.DEVICEEVENT:
|
||||||
|
if (DGControl.DeviceEvent.Get(ref _appIdentity, ref _currentDS, out TW_DEVICEEVENT de) == STS.SUCCESS)
|
||||||
|
{
|
||||||
|
_uiThreadMarshaller.BeginInvoke(() =>
|
||||||
|
{
|
||||||
|
DeviceEvent?.Invoke(this, de);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MSG.XFERREADY:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Debug.WriteLine("Ending BackgroundThreadLoop...");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposedValue)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
// this will end the bg thread
|
||||||
|
_bgPendingMsgs.CompleteAdding();
|
||||||
|
}
|
||||||
|
disposedValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
||||||
|
// ~TwainSession()
|
||||||
|
// {
|
||||||
|
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
|
// Dispose(disposing: false);
|
||||||
|
// }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads and opens the TWAIN data source manager.
|
/// Loads and opens the TWAIN data source manager.
|
||||||
|
Loading…
Reference in New Issue
Block a user