Started adding the higher level wrapper TwainSession.

This commit is contained in:
Eugene Wang 2021-04-21 11:39:37 -04:00
parent 0cd99efc0a
commit 20216b52a7
5 changed files with 384 additions and 2 deletions

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NTwain.Internal
{
static class TwainUtility
{
}
}

View File

@ -4,7 +4,7 @@
<PackageId>NTwain</PackageId>
<Version>4.0.0</Version>
<Description>Library containing the TWAIN API for dotnet.</Description>
<TargetFrameworks>net45;netstandard2.0;</TargetFrameworks>
<TargetFrameworks>net45;netcoreapp3.1;net5.0-windows</TargetFrameworks>
<PackageProjectUrl>https://github.com/soukoku/ntwain</PackageProjectUrl>
<PackageTags>twain scan</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
@ -17,6 +17,8 @@
<AssemblyVersion>4.0.0.0</AssemblyVersion>
<FileVersion>4.0.0.0</FileVersion>
<LangVersion>7.1</LangVersion>
<UseWindowsForms>true</UseWindowsForms>
<UseWPF>true</UseWPF>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">

104
NTwain/ThreadMarshaller.cs Normal file
View File

@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace NTwain
{
/// <summary>
/// Allows work to be marshalled to a different (usually UI) thread if necessary.
/// </summary>
public interface IThreadMarshaller
{
/// <summary>
/// Starts work asynchronously and returns immediately.
/// </summary>
/// <param name="work"></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>
/// Doesn't actually use any particular thread.
/// Should only be used in non-UI apps.
/// </summary>
public class NoParticularMarshaller : IThreadMarshaller
{
public bool InvokeRequired => throw new NotImplementedException();
public void BeginInvoke(Delegate work, params object[] args)
{
Task.Run(() => work.DynamicInvoke(args));
}
public object Invoke(Delegate work, params object[] args)
{
return work.DynamicInvoke(args);
}
}
/// <summary>
/// Uses a winform UI thread to do the work.
/// </summary>
public class WinformMarshaller : IThreadMarshaller
{
private readonly System.Windows.Forms.Control _uiControl;
/// <summary>
/// Uses a control whose UI thread is used to run the work.
/// </summary>
/// <param name="uiControl"></param>
public WinformMarshaller(System.Windows.Forms.Control uiControl)
{
_uiControl = uiControl ?? throw new ArgumentNullException(nameof(uiControl));
}
public void BeginInvoke(Delegate work, params object[] args)
{
_uiControl.BeginInvoke(work, args);
}
public object Invoke(Delegate work, params object[] args)
{
return _uiControl.Invoke(work, args);
}
}
/// <summary>
/// Uses a WPF dispatcher to do the work.
/// </summary>
public class DispatcherMarshaller : IThreadMarshaller
{
private readonly System.Windows.Threading.Dispatcher _dispatcher;
/// <summary>
/// Uses a dispatcher whose UI thread is used to run the work.
/// </summary>
/// <param name="dispatcher"></param>
public DispatcherMarshaller(System.Windows.Threading.Dispatcher dispatcher)
{
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(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);
}
}
}

263
NTwain/TwainSession.cs Normal file
View File

@ -0,0 +1,263 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using TWAINWorkingGroup;
namespace NTwain
{
public class TwainSession : IDisposable
{
private TWAIN _twain;
private bool _disposed;
private readonly IThreadMarshaller _threadMarshaller;
private IntPtr _hWnd;
public TwainSession(Assembly application,
IThreadMarshaller threadMarshaller, IntPtr hWnd,
TWLG language = TWLG.ENGLISH_USA, TWCY country = TWCY.USA)
{
var info = FileVersionInfo.GetVersionInfo(application.Location);
_twain = new TWAIN(info.CompanyName, info.ProductName, info.ProductName,
(ushort)TWON_PROTOCOL.MAJOR, (ushort)TWON_PROTOCOL.MINOR,
(uint)(DG.APP2 | DG.IMAGE),
country, "", language, 2, 4, false, true, HandleDeviceEvent, HandleScanEvent, HandleUIThreadAction, hWnd);
_threadMarshaller = threadMarshaller ?? new NoParticularMarshaller();
_hWnd = hWnd;
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
if (_twain != null)
{
Close();
_twain.Dispose();
_twain = null;
}
}
_disposed = true;
}
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
#region event callbacks
private void HandleUIThreadAction(Action action)
{
_threadMarshaller.Invoke(action);
}
private STS HandleScanEvent(bool a_blClosing)
{
_threadMarshaller.BeginInvoke(new Action<bool>(RaiseScanEvent), a_blClosing);
return STS.SUCCESS;
}
void RaiseScanEvent(bool closing)
{
OnScanEvent(closing);
ScanEvent?.Invoke(this, closing);
}
protected virtual void OnScanEvent(bool closing) { }
public event EventHandler<bool> ScanEvent;
private STS HandleDeviceEvent()
{
STS sts;
TW_DEVICEEVENT twdeviceevent;
// Drain the event queue...
while (true)
{
// Try to get an event...
twdeviceevent = default(TW_DEVICEEVENT);
sts = _twain.DatDeviceevent(DG.CONTROL, MSG.GET, ref twdeviceevent);
if (sts != STS.SUCCESS)
{
break;
}
else
{
RaiseDeviceEvent(twdeviceevent);
}
}
// Return a status, in case we ever need it for anything...
return STS.SUCCESS;
}
void RaiseDeviceEvent(TW_DEVICEEVENT twdeviceevent)
{
OnDeviceEvent(twdeviceevent);
DeviceEvent?.Invoke(this, twdeviceevent);
}
protected virtual void OnDeviceEvent(TW_DEVICEEVENT twdeviceevent) { }
public event EventHandler<TW_DEVICEEVENT> DeviceEvent;
#endregion
#region TWAIN operations
/// <summary>
/// Gets the current TWAIN state.
/// </summary>
public STATE State
{
get { return _twain.GetState(); }
}
/// <summary>
/// Opens the TWAIN data source manager.
/// This needs to be done before anything else.
/// </summary>
/// <returns></returns>
public STS Open()
{
var sts = _twain.DatParent(DG.CONTROL, MSG.OPENDSM, ref _hWnd);
return sts;
}
/// <summary>
/// Closes the TWAIN data source manager.
/// </summary>
public void Close()
{
StepDown(STATE.S2);
}
/// <summary>
/// Gets list of TWAIN devices.
/// </summary>
/// <returns></returns>
public IList<TW_IDENTITY> GetDevices()
{
var list = new List<TW_IDENTITY>();
if (State > STATE.S2)
{
var twidentity = default(TW_IDENTITY);
STS sts;
for (sts = _twain.DatIdentity(DG.CONTROL, MSG.GETFIRST, ref twidentity);
sts != STS.ENDOFLIST;
sts = _twain.DatIdentity(DG.CONTROL, MSG.GETNEXT, ref twidentity))
{
list.Add(twidentity);
}
}
return list;
}
/// <summary>
/// Gets or sets the default device.
/// </summary>
public TW_IDENTITY? DefaultDevice
{
get
{
var twidentity = default(TW_IDENTITY);
var sts = _twain.DatIdentity(DG.CONTROL, MSG.GETDEFAULT, ref twidentity);
if (sts == STS.SUCCESS) return twidentity;
return null;
}
set
{
// Make it the default, we don't care if this succeeds...
if (value.HasValue)
{
var twidentity = value.Value;
_twain.DatIdentity(DG.CONTROL, MSG.SET, ref twidentity);
}
}
}
/// <summary>
/// Gets the currently open device.
/// </summary>
public TW_IDENTITY? CurrentDevice
{
get
{
if (State > STATE.S3)
{
var twidentity = default(TW_IDENTITY);
if (TWAIN.CsvToIdentity(ref twidentity, _twain.GetDsIdentity()))
{
return twidentity;
}
}
return null;
}
}
/// <summary>
/// Steps down the TWAIN state to the specified state.
/// </summary>
/// <param name="state"></param>
public void StepDown(STATE state)
{
var twpendingxfers = default(TW_PENDINGXFERS);
// Make sure we have something to work with...
if (_twain == null)
{
return;
}
// Walk the states, we don't care about the status returns. Basically,
// these need to work, or we're guaranteed to hang...
// 7 --> 6
if ((_twain.GetState() == STATE.S7) && (state < STATE.S7))
{
_twain.DatPendingxfers(DG.CONTROL, MSG.ENDXFER, ref twpendingxfers);
}
// 6 --> 5
if ((_twain.GetState() == STATE.S6) && (state < STATE.S6))
{
_twain.DatPendingxfers(DG.CONTROL, MSG.RESET, ref twpendingxfers);
}
// 5 --> 4
if ((_twain.GetState() == STATE.S5) && (state < STATE.S5))
{
var twuserinterface = default(TW_USERINTERFACE);
_twain.DatUserinterface(DG.CONTROL, MSG.DISABLEDS, ref twuserinterface);
}
// 4 --> 3
if ((_twain.GetState() == STATE.S4) && (state < STATE.S4))
{
var twidentity = default(TW_IDENTITY);
TWAIN.CsvToIdentity(ref twidentity, _twain.GetDsIdentity());
_twain.DatIdentity(DG.CONTROL, MSG.CLOSEDS, ref twidentity);
}
// 3 --> 2
if ((_twain.GetState() == STATE.S3) && (state < STATE.S3))
{
_twain.DatParent(DG.CONTROL, MSG.CLOSEDSM, ref _hWnd);
}
}
#endregion
}
}

View File

@ -13,7 +13,7 @@ V4 of this lib has these goals:
* Targets latest TWAIN version (2.4 as of this writing).
* Supports all the TWAIN functions in the spec (directly or through dotnet wrapper).
* Works with both 32 or 64 bit data sources as appropriate for the 32 or 64 bit apps.
* Supports full framework (4.5+) and netcore (2+) apps.
* Supports full framework (4.5+) and netcore (3.1+) apps.
## Using the lib