mirror of
https://github.com/soukoku/ntwain.git
synced 2025-07-15 12:21:37 +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;
|
||||
|
||||
var twain = new TwainSession(Assembly.GetExecutingAssembly().Location);
|
||||
var twain = new TwainSession(new InPlaceMarshaller(), Assembly.GetExecutingAssembly().Location);
|
||||
twain.StateChanged += Twain_StateChanged;
|
||||
|
||||
var hwnd = IntPtr.Zero; // required for windows
|
||||
|
@ -18,10 +18,17 @@ namespace WinForm32
|
||||
Text += TwainPlatform.Is32bit ? " 32bit" : " 64bit";
|
||||
TwainPlatform.PreferLegacyDSM = false;
|
||||
|
||||
twain = new TwainSession(Assembly.GetExecutingAssembly().Location);
|
||||
twain = new TwainSession(new WinformMarshaller(this), Assembly.GetExecutingAssembly().Location);
|
||||
twain.StateChanged += Twain_StateChanged;
|
||||
twain.DefaultSourceChanged += Twain_DefaultSourceChanged;
|
||||
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)
|
||||
|
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)
|
||||
{
|
||||
case MSG.XFERREADY:
|
||||
break;
|
||||
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:
|
||||
DisableSource();
|
||||
break;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -130,5 +130,10 @@ namespace NTwain
|
||||
/// Fires when <see cref="CurrentSource"/> changes.
|
||||
/// </summary>
|
||||
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.Triplets;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace NTwain
|
||||
{
|
||||
// this file contains initialization/cleanup things.
|
||||
|
||||
public partial class TwainSession
|
||||
public partial class TwainSession : IDisposable
|
||||
{
|
||||
static bool __encodingRegistered;
|
||||
|
||||
/// <summary>
|
||||
/// Creates TWAIN session with app info derived an executable file.
|
||||
/// </summary>
|
||||
/// <param name="uiThreadMarshaller"></param>
|
||||
/// <param name="exeFilePath"></param>
|
||||
/// <param name="appLanguage"></param>
|
||||
/// <param name="appCountry"></param>
|
||||
public TwainSession(string exeFilePath,
|
||||
TWLG appLanguage = TWLG.ENGLISH_USA, TWCY appCountry = TWCY.USA) :
|
||||
this(FileVersionInfo.GetVersionInfo(exeFilePath),
|
||||
appLanguage, appCountry)
|
||||
public TwainSession(IThreadMarshaller uiThreadMarshaller,
|
||||
string exeFilePath,
|
||||
TWLG appLanguage = TWLG.ENGLISH_USA, TWCY appCountry = TWCY.USA) :
|
||||
this(uiThreadMarshaller, FileVersionInfo.GetVersionInfo(exeFilePath), appLanguage, appCountry)
|
||||
{ }
|
||||
/// <summary>
|
||||
/// Creates TWAIN session with app info derived from a <see cref="FileVersionInfo"/> object.
|
||||
/// </summary>
|
||||
/// <param name="uiThreadMarshaller"></param>
|
||||
/// <param name="appInfo"></param>
|
||||
/// <param name="appLanguage"></param>
|
||||
/// <param name="appCountry"></param>
|
||||
public TwainSession(FileVersionInfo appInfo,
|
||||
public TwainSession(IThreadMarshaller uiThreadMarshaller,
|
||||
FileVersionInfo appInfo,
|
||||
TWLG appLanguage = TWLG.ENGLISH_USA, TWCY appCountry = TWCY.USA) :
|
||||
this(appInfo.CompanyName ?? "",
|
||||
this(uiThreadMarshaller,
|
||||
appInfo.CompanyName ?? "",
|
||||
appInfo.ProductName ?? "",
|
||||
appInfo.ProductName ?? "",
|
||||
new Version(appInfo.FileVersion ?? "1.0"),
|
||||
@ -40,6 +46,7 @@ namespace NTwain
|
||||
/// <summary>
|
||||
/// Creates TWAIN session with explicit app info.
|
||||
/// </summary>
|
||||
/// <param name="uiThreadMarshaller"></param>
|
||||
/// <param name="companyName"></param>
|
||||
/// <param name="productFamily"></param>
|
||||
/// <param name="productName"></param>
|
||||
@ -48,11 +55,13 @@ namespace NTwain
|
||||
/// <param name="appLanguage"></param>
|
||||
/// <param name="appCountry"></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 = "",
|
||||
TWLG appLanguage = TWLG.ENGLISH_USA, TWCY appCountry = TWCY.USA,
|
||||
DG supportedTypes = DG.IMAGE)
|
||||
{
|
||||
// todo: find a better place for this
|
||||
if (!__encodingRegistered)
|
||||
{
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
@ -79,10 +88,86 @@ namespace NTwain
|
||||
|
||||
_legacyCallbackDelegate = LegacyCallbackHandler;
|
||||
_osxCallbackDelegate = OSXCallbackHandler;
|
||||
|
||||
_uiThreadMarshaller = uiThreadMarshaller;
|
||||
StartBackgroundThread();
|
||||
}
|
||||
|
||||
internal IntPtr _hwnd;
|
||||
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>
|
||||
/// Loads and opens the TWAIN data source manager.
|
||||
|
Loading…
Reference in New Issue
Block a user