From 1610e39d2db8e8408fbbb9eb152a823cb509bd1d Mon Sep 17 00:00:00 2001 From: Eugene Wang <8755753+soukoku@users.noreply.github.com> Date: Fri, 31 Mar 2023 08:10:37 -0400 Subject: [PATCH] Updated readme and added some more ctors. --- LICENSE.txt | 30 +- README.md | 15 +- src/NTwain/Extensions/TWAINH_EXTRAS.cs | 459 ++++++++++ src/NTwain/NTwain.csproj | 4 +- src/NTwain/TWAINH_EXTRAS.cs | 462 ----------- src/NTwain/TwainSession.cs | 1057 ++++++++++++------------ 6 files changed, 1005 insertions(+), 1022 deletions(-) create mode 100644 src/NTwain/Extensions/TWAINH_EXTRAS.cs delete mode 100644 src/NTwain/TWAINH_EXTRAS.cs diff --git a/LICENSE.txt b/LICENSE.txt index 2e03de3..d7972fd 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,8 +1,32 @@ The MIT License (MIT) -Copyright (c) 2012 - 2021 Eugene Wang - -Copyright(C) 2013 - 2020 Kodak Alaris Inc. +Copyright (c) 2012 - 2023 Eugene Wang + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + + + +# for twaincs + + +Copyright(C) 2013 - 2021 Kodak Alaris Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/README.md b/README.md index cae2fdf..2abd0cf 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,18 @@ # TWAIN dotnet library -NOTE: This is a rewrite test that's based off twaincs from -the TWAIN Working Group and doesn't fully work yet. -Use V3 branch for current version. +NOTE: This is a rewrite test that internally uses +[twaincs](https://github.com/twain/twain-cs) from +the TWAIN Working Group. It doesn't fully work yet. +Use V3 branch for the current version. ## Info This is a dotnet library created to make working with -[TWAIN](http://twain.org/) easier. +[TWAIN](http://twain.org/) devices easier in dotnet. 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 apps. +* Targets latest TWAIN version (2.5). +* Supports full framework and netcore apps (if the framework is still supported). ## Using the lib diff --git a/src/NTwain/Extensions/TWAINH_EXTRAS.cs b/src/NTwain/Extensions/TWAINH_EXTRAS.cs new file mode 100644 index 0000000..840f367 --- /dev/null +++ b/src/NTwain/Extensions/TWAINH_EXTRAS.cs @@ -0,0 +1,459 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using static TWAINWorkingGroup.TWAIN; + +namespace TWAINWorkingGroup +{ + // this contains my additions under the TWAINWorkingGroup namespace + // that makes some twain types easier to work with. + + /// + /// TWAIN's boolean values. + /// + public enum BoolType : ushort + { + /// + /// The false value (0). + /// + False = 0, + /// + /// The true value (1). + /// + True = 1 + } + + /// + /// A more dotnet-friendly representation of . + /// + public struct Metrics + { + /// + /// Return code of querying the metrics. + /// + public STS ReturnCode; + + /// + /// The number of sheets of paper processed by the scanner. + /// + public int Sheets; + + /// + /// The number of images made available for transfer by the driver. This is not + /// necessarily the same as the number of images actually transferred, since the + /// application may opt to skip transfers or to end without transferring all images. + /// + public int Images; + } + + public struct TwainDirectTaskResult + { + /// + /// Return code of task. + /// + public STS ReturnCode; + + /// + /// The response of the task in JSON if successful. + /// + public string ResponseJson; + } + + /// + /// A more dotnet-friendly representation of . + /// + /// + public class Enumeration where TValue : struct + { + public int CurrentIndex; + + public int DefaultIndex; + + public TValue[] Items; + } + + /// + /// A more dotnet-friendly representation of . + /// + /// + public partial class Range : IEnumerable where TValue : struct + { + public TValue MinValue; + public TValue MaxValue; + public TValue StepSize; + public TValue DefaultValue; + public TValue CurrentValue; + + IEnumerator IEnumerable.GetEnumerator() + { + if (!(MinValue is IConvertible)) + throw new NotSupportedException($"The value type {typeof(TValue).Name} is not supported for range enumeration."); + + return new DynamicEnumerator(MinValue, MaxValue, StepSize); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)this).GetEnumerator(); + } + + // dynamic is a cheap hack to sidestep the compiler restrictions if I know TValue is numeric + class DynamicEnumerator : IEnumerator + { + private readonly TValue _min; + private readonly TValue _max; + private readonly TValue _step; + private TValue _cur; + bool started = false; + + public DynamicEnumerator(TValue min, TValue max, TValue step) + { + _min = min; + _max = max; + _step = step; + _cur = min; + } + + public TValue Current => _cur; + + object IEnumerator.Current => this.Current; + + public void Dispose() { } + + public bool MoveNext() + { + if (!started) + { + started = true; + return true; + } + + var next = _cur + (dynamic)_step; + if (next == _cur || next < _min || next > _max) return false; + + _cur = next; + return true; + } + + public void Reset() + { + _cur = _min; + started = false; + } + } + } + + partial class TWAIN + { + partial struct TW_FIX32 : IEquatable, IConvertible + { + // the conversion logic is found in the spec. + + float ToFloat() + { + return Whole + Frac / 65536f; + } + double ToDouble() + { + return Whole + Frac / 65536.0; + } + public TW_FIX32(double value) + { + Whole = (short)value; + Frac = (ushort)((value - Whole) * 65536.0); + } + public TW_FIX32(float value) + { + //int temp = (int)(value * 65536.0 + 0.5); + //Whole = (short)(temp >> 16); + //Fraction = (ushort)(temp & 0x0000ffff); + + // different version from twain faq + bool sign = value < 0; + int temp = (int)(value * 65536.0 + (sign ? (-0.5) : 0.5)); + Whole = (short)(temp >> 16); + Frac = (ushort)(temp & 0x0000ffff); + } + + public override string ToString() + { + return ToFloat().ToString(); + } + + public bool Equals(TW_FIX32 other) + { + return Whole == other.Whole && Frac == other.Frac; + } + public override bool Equals(object obj) + { + if (obj is TW_FIX32 other) + { + return Equals(other); + } + return false; + } + public override int GetHashCode() + { + return Whole ^ Frac; + } + + + #region IConvertable + + TypeCode IConvertible.GetTypeCode() + { + return TypeCode.Single; + } + + bool IConvertible.ToBoolean(IFormatProvider provider) + { + return this != 0; + } + + byte IConvertible.ToByte(IFormatProvider provider) + { + return Convert.ToByte((float)this); + } + + char IConvertible.ToChar(IFormatProvider provider) + { + return Convert.ToChar((float)this); + } + + DateTime IConvertible.ToDateTime(IFormatProvider provider) + { + return Convert.ToDateTime((float)this); + } + + decimal IConvertible.ToDecimal(IFormatProvider provider) + { + return Convert.ToDecimal((float)this); + } + + double IConvertible.ToDouble(IFormatProvider provider) + { + return Convert.ToDouble((float)this); + } + + short IConvertible.ToInt16(IFormatProvider provider) + { + return Convert.ToInt16((float)this); + } + + int IConvertible.ToInt32(IFormatProvider provider) + { + return Convert.ToInt32((float)this); + } + + long IConvertible.ToInt64(IFormatProvider provider) + { + return Convert.ToInt64((float)this); + } + + sbyte IConvertible.ToSByte(IFormatProvider provider) + { + return Convert.ToSByte((float)this); + } + + float IConvertible.ToSingle(IFormatProvider provider) + { + return Convert.ToSingle((float)this); + } + + string IConvertible.ToString(IFormatProvider provider) + { + return this.ToString(); + } + + object IConvertible.ToType(Type conversionType, IFormatProvider provider) + { + return Convert.ChangeType((float)this, conversionType, CultureInfo.InvariantCulture); + } + + ushort IConvertible.ToUInt16(IFormatProvider provider) + { + return Convert.ToUInt16((float)this); + } + + uint IConvertible.ToUInt32(IFormatProvider provider) + { + return Convert.ToUInt32((float)this); + } + + ulong IConvertible.ToUInt64(IFormatProvider provider) + { + return Convert.ToUInt64((float)this); + } + + #endregion + + public static implicit operator float(TW_FIX32 value) => value.ToFloat(); + public static implicit operator TW_FIX32(float value) => new TW_FIX32(value); + + public static implicit operator double(TW_FIX32 value) => value.ToDouble(); + public static implicit operator TW_FIX32(double value) => new TW_FIX32((float)value); + + public static bool operator ==(TW_FIX32 value1, TW_FIX32 value2) => value1.Equals(value2); + public static bool operator !=(TW_FIX32 value1, TW_FIX32 value2) => !value1.Equals(value2); + } + + partial struct TW_FRAME : IEquatable + { + /// + /// Creates from a string representation of it. + /// + /// + public TW_FRAME(string value) : this() + { + var parts = value.Split(','); + if (parts.Length == 4) + { + Left = float.Parse(parts[0]); + Top = float.Parse(parts[1]); + Right = float.Parse(parts[2]); + Bottom = float.Parse(parts[3]); + } + else + { + throw new ArgumentException($"Cannot create frame from \"{value}\"."); + } + } + + /// + /// String representation of Left,Top,Right,Bottom. + /// + /// + public override string ToString() + { + return $"{Left},{Top},{Right},{Bottom}"; + } + + public bool Equals(TW_FRAME other) + { + return Left == other.Left && Top == other.Top && + Right == other.Right && Bottom == other.Bottom; + } + + public override bool Equals(object obj) + { + if (obj is TW_FRAME other) + { + return Equals(other); + } + return false; + } + + public override int GetHashCode() + { + return Left.GetHashCode() ^ Top.GetHashCode() ^ + Right.GetHashCode() ^ Bottom.GetHashCode(); + } + + + public static bool operator ==(TW_FRAME value1, TW_FRAME value2) + { + return value1.Equals(value2); + } + public static bool operator !=(TW_FRAME value1, TW_FRAME value2) + { + return !value1.Equals(value2); + } + } + + partial struct TW_STR32 + { + public const int Size = 34; + + public TW_STR32(string value) : this() + { + Set(value); + } + + public override string ToString() + { + return Get(); + } + + public static implicit operator string(TW_STR32 value) => value.ToString(); + public static explicit operator TW_STR32(string value) => new TW_STR32(value); + + } + + partial struct TW_STR64 + { + public const int Size = 66; + + public TW_STR64(string value) : this() + { + Set(value); + } + + public override string ToString() + { + return Get(); + } + + public static implicit operator string(TW_STR64 value) => value.ToString(); + public static explicit operator TW_STR64(string value) => new TW_STR64(value); + } + + partial struct TW_STR128 + { + public const int Size = 130; + + public TW_STR128(string value) : this() + { + Set(value); + } + + public override string ToString() + { + return Get(); + } + + public static implicit operator string(TW_STR128 value) => value.ToString(); + public static explicit operator TW_STR128(string value) => new TW_STR128(value); + } + + partial struct TW_STR255 + { + public const int Size = 256; + + public TW_STR255(string value) : this() + { + Set(value); + } + + public override string ToString() + { + return Get(); + } + + public static implicit operator string(TW_STR255 value) => value.ToString(); + public static explicit operator TW_STR255(string value) => new TW_STR255(value); + } + + partial struct TW_IDENTITY + { + public override string ToString() + { + return $"{Manufacturer} - {ProductName} {Version} (TWAIN {ProtocolMajor}.{ProtocolMinor})"; + } + } + + partial struct TW_VERSION + { + public override string ToString() + { + return $"{MajorNum}.{MinorNum}"; + } + } + + //partial struct TW_DEVICEEVENT + //{ + // public TWDE Event { get { return (TWDE)_event; } } + // public TWFL FlashUsed2 { get { return (TWFL)_flashUsed2; } } + //} + } +} diff --git a/src/NTwain/NTwain.csproj b/src/NTwain/NTwain.csproj index 87f34d8..6e09cf0 100644 --- a/src/NTwain/NTwain.csproj +++ b/src/NTwain/NTwain.csproj @@ -3,9 +3,9 @@ NTwain Library containing the TWAIN API for dotnet. - net462;netcoreapp3.1;net5.0;net6.0 + net462;netcoreapp3.1;net5.0;net6.0;net7.0;netstandard2.0 true - 10.0 + 11 diff --git a/src/NTwain/TWAINH_EXTRAS.cs b/src/NTwain/TWAINH_EXTRAS.cs deleted file mode 100644 index 63b0cfb..0000000 --- a/src/NTwain/TWAINH_EXTRAS.cs +++ /dev/null @@ -1,462 +0,0 @@ -using NTwain; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using static TWAINWorkingGroup.TWAIN; - -namespace TWAINWorkingGroup -{ - // contains my additions that makes twain types easier to work with. - - /// - /// TWAIN's boolean values. - /// - public enum BoolType : ushort - { - /// - /// The false value (0). - /// - False = 0, - /// - /// The true value (1). - /// - True = 1 - } - - /// - /// A more dotnet-friendly representation of . - /// - public struct Metrics - { - /// - /// Return code of querying the metrics. - /// - public STS ReturnCode; - - /// - /// The number of sheets of paper processed by the scanner. - /// - public int Sheets; - - /// - /// The number of images made available for transfer by the driver. This is not - /// necessarily the same as the number of images actually transferred, since the - /// application may opt to skip transfers or to end without transferring all images. - /// - public int Images; - } - - public struct TwainDirectTaskResult - { - /// - /// Return code of task. - /// - public STS ReturnCode; - - /// - /// The response of the task in JSON if successful. - /// - public string ResponseJson; - } - - /// - /// A more dotnet-friendly representation of . - /// - /// - public class Enumeration where TValue : struct - { - public int CurrentIndex; - - public int DefaultIndex; - - public TValue[] Items; - } - - /// - /// A more dotnet-friendly representation of . - /// - /// - public partial class Range : IEnumerable where TValue : struct - { - public TValue MinValue; - public TValue MaxValue; - public TValue StepSize; - public TValue DefaultValue; - public TValue CurrentValue; - - IEnumerator IEnumerable.GetEnumerator() - { - if (!(MinValue is IConvertible)) - throw new NotSupportedException($"The value type {typeof(TValue).Name} is not supported for range enumeration."); - - return new DynamicEnumerator(MinValue, MaxValue, StepSize); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)this).GetEnumerator(); - } - - // dynamic is a cheap hack to sidestep the compiler restrictions if I know TValue is numeric - class DynamicEnumerator : IEnumerator - { - private readonly TValue _min; - private readonly TValue _max; - private readonly TValue _step; - private TValue _cur; - bool started = false; - - public DynamicEnumerator(TValue min, TValue max, TValue step) - { - _min = min; - _max = max; - _step = step; - _cur = min; - } - - public TValue Current => _cur; - - object IEnumerator.Current => this.Current; - - public void Dispose() { } - - public bool MoveNext() - { - if (!started) - { - started = true; - return true; - } - - var next = _cur + (dynamic)_step; - if (next == _cur || next < _min || next > _max) return false; - - _cur = next; - return true; - } - - public void Reset() - { - _cur = _min; - started = false; - } - } - } - - partial class TWAIN - { - partial struct TW_FIX32 : IEquatable, IConvertible - { - // the conversion logic is found in the spec. - - float ToFloat() - { - return Whole + Frac / 65536f; - } - double ToDouble() - { - return Whole + Frac / 65536.0; - } - public TW_FIX32(double value) - { - Whole = (short)value; - Frac = (ushort)((value - Whole) * 65536.0); - } - public TW_FIX32(float value) - { - //int temp = (int)(value * 65536.0 + 0.5); - //Whole = (short)(temp >> 16); - //Fraction = (ushort)(temp & 0x0000ffff); - - // different version from twain faq - bool sign = value < 0; - int temp = (int)(value * 65536.0 + (sign ? (-0.5) : 0.5)); - Whole = (short)(temp >> 16); - Frac = (ushort)(temp & 0x0000ffff); - } - - public override string ToString() - { - return ToFloat().ToString(); - } - - public bool Equals(TW_FIX32 other) - { - return Whole == other.Whole && Frac == other.Frac; - } - public override bool Equals(object obj) - { - if (obj is TW_FIX32 other) - { - return Equals(other); - } - return false; - } - public override int GetHashCode() - { - return Whole ^ Frac; - } - - - #region IConvertable - - TypeCode IConvertible.GetTypeCode() - { - return TypeCode.Single; - } - - bool IConvertible.ToBoolean(IFormatProvider provider) - { - return this != 0; - } - - byte IConvertible.ToByte(IFormatProvider provider) - { - return Convert.ToByte((float)this); - } - - char IConvertible.ToChar(IFormatProvider provider) - { - return Convert.ToChar((float)this); - } - - DateTime IConvertible.ToDateTime(IFormatProvider provider) - { - return Convert.ToDateTime((float)this); - } - - decimal IConvertible.ToDecimal(IFormatProvider provider) - { - return Convert.ToDecimal((float)this); - } - - double IConvertible.ToDouble(IFormatProvider provider) - { - return Convert.ToDouble((float)this); - } - - short IConvertible.ToInt16(IFormatProvider provider) - { - return Convert.ToInt16((float)this); - } - - int IConvertible.ToInt32(IFormatProvider provider) - { - return Convert.ToInt32((float)this); - } - - long IConvertible.ToInt64(IFormatProvider provider) - { - return Convert.ToInt64((float)this); - } - - sbyte IConvertible.ToSByte(IFormatProvider provider) - { - return Convert.ToSByte((float)this); - } - - float IConvertible.ToSingle(IFormatProvider provider) - { - return Convert.ToSingle((float)this); - } - - string IConvertible.ToString(IFormatProvider provider) - { - return this.ToString(); - } - - object IConvertible.ToType(Type conversionType, IFormatProvider provider) - { - return Convert.ChangeType((float)this, conversionType, CultureInfo.InvariantCulture); - } - - ushort IConvertible.ToUInt16(IFormatProvider provider) - { - return Convert.ToUInt16((float)this); - } - - uint IConvertible.ToUInt32(IFormatProvider provider) - { - return Convert.ToUInt32((float)this); - } - - ulong IConvertible.ToUInt64(IFormatProvider provider) - { - return Convert.ToUInt64((float)this); - } - - #endregion - - public static implicit operator float(TW_FIX32 value) => value.ToFloat(); - public static implicit operator TW_FIX32(float value) => new TW_FIX32(value); - - public static implicit operator double(TW_FIX32 value) => value.ToDouble(); - public static implicit operator TW_FIX32(double value) => new TW_FIX32((float)value); - - public static bool operator ==(TW_FIX32 value1, TW_FIX32 value2) => value1.Equals(value2); - public static bool operator !=(TW_FIX32 value1, TW_FIX32 value2) => !value1.Equals(value2); - } - - partial struct TW_FRAME : IEquatable - { - /// - /// Creates from a string representation of it. - /// - /// - public TW_FRAME(string value) : this() - { - var parts = value.Split(','); - if (parts.Length == 4) - { - Left = float.Parse(parts[0]); - Top = float.Parse(parts[1]); - Right = float.Parse(parts[2]); - Bottom = float.Parse(parts[3]); - } - else - { - throw new ArgumentException($"Cannot create frame from \"{value}\"."); - } - } - - /// - /// String representation of Left,Top,Right,Bottom. - /// - /// - public override string ToString() - { - return $"{Left},{Top},{Right},{Bottom}"; - } - - public bool Equals(TW_FRAME other) - { - return Left == other.Left && Top == other.Top && - Right == other.Right && Bottom == other.Bottom; - } - - public override bool Equals(object obj) - { - if (obj is TW_FRAME other) - { - return Equals(other); - } - return false; - } - - public override int GetHashCode() - { - return Left.GetHashCode() ^ Top.GetHashCode() ^ - Right.GetHashCode() ^ Bottom.GetHashCode(); - } - - - public static bool operator ==(TW_FRAME value1, TW_FRAME value2) - { - return value1.Equals(value2); - } - public static bool operator !=(TW_FRAME value1, TW_FRAME value2) - { - return !value1.Equals(value2); - } - } - - partial struct TW_STR32 - { - public const int Size = 34; - - public TW_STR32(string value) : this() - { - Set(value); - } - - public override string ToString() - { - return Get(); - } - - public static implicit operator string(TW_STR32 value) => value.ToString(); - public static explicit operator TW_STR32(string value) => new TW_STR32(value); - - } - - partial struct TW_STR64 - { - public const int Size = 66; - - public TW_STR64(string value) : this() - { - Set(value); - } - - public override string ToString() - { - return Get(); - } - - public static implicit operator string(TW_STR64 value) => value.ToString(); - public static explicit operator TW_STR64(string value) => new TW_STR64(value); - } - - partial struct TW_STR128 - { - public const int Size = 130; - - public TW_STR128(string value) : this() - { - Set(value); - } - - public override string ToString() - { - return Get(); - } - - public static implicit operator string(TW_STR128 value) => value.ToString(); - public static explicit operator TW_STR128(string value) => new TW_STR128(value); - } - - partial struct TW_STR255 - { - public const int Size = 256; - - public TW_STR255(string value) : this() - { - Set(value); - } - - public override string ToString() - { - return Get(); - } - - public static implicit operator string(TW_STR255 value) => value.ToString(); - public static explicit operator TW_STR255(string value) => new TW_STR255(value); - } - - partial struct TW_IDENTITY - { - public override string ToString() - { - return $"{Manufacturer} - {ProductName} {Version} (TWAIN {ProtocolMajor}.{ProtocolMinor})"; - } - } - - partial struct TW_VERSION - { - public override string ToString() - { - return $"{MajorNum}.{MinorNum}"; - } - } - - //partial struct TW_DEVICEEVENT - //{ - // public TWDE Event { get { return (TWDE)_event; } } - // public TWFL FlashUsed2 { get { return (TWFL)_flashUsed2; } } - //} - } -} diff --git a/src/NTwain/TwainSession.cs b/src/NTwain/TwainSession.cs index a891358..5de1e16 100644 --- a/src/NTwain/TwainSession.cs +++ b/src/NTwain/TwainSession.cs @@ -1,601 +1,564 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; -using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Text; using System.Threading; -using System.Threading.Tasks; using TWAINWorkingGroup; using static TWAINWorkingGroup.TWAIN; namespace NTwain { - public class TwainSession : IDisposable + /// + /// A wrapper around the low-level object + /// that may be easier to use in dotnet with typical scenarios. + /// + public class TwainSession : IDisposable + { + private TWAIN _twain; + private bool _disposed; + private readonly IThreadMarshaller _threadMarshaller; + private IntPtr _hWnd; + + public TwainSession(Assembly applicationInfo, + IThreadMarshaller threadMarshaller, IntPtr hWnd, + TWLG language = TWLG.ENGLISH_USA, TWCY country = TWCY.USA) : + this(FileVersionInfo.GetVersionInfo(applicationInfo.Location), + threadMarshaller, hWnd, language, country) + { } + public TwainSession(FileVersionInfo applicationInfo, + IThreadMarshaller threadMarshaller, IntPtr hWnd, + TWLG language = TWLG.ENGLISH_USA, TWCY country = TWCY.USA) : + this(applicationInfo.CompanyName, applicationInfo.ProductName, applicationInfo.ProductName, + threadMarshaller, hWnd, language, country) + { } + public TwainSession(string companyName, string productFamily, string productName, + IThreadMarshaller threadMarshaller, IntPtr hWnd, + TWLG language = TWLG.ENGLISH_USA, TWCY country = TWCY.USA) { - private TWAIN _twain; - private bool _disposed; - private readonly IThreadMarshaller _threadMarshaller; - private IntPtr _hWnd; + _twain = new TWAIN( + companyName, productFamily, productName, + (ushort)TWON_PROTOCOL.MAJOR, (ushort)TWON_PROTOCOL.MINOR, + (uint)(DG.APP2 | DG.IMAGE), + country, "", language, 2, 4, false, true, + HandleDeviceEvent, + HandleScanEvent, + HandleUIThreadAction, + hWnd); - public TwainSession(Assembly application, - IThreadMarshaller threadMarshaller, IntPtr hWnd, - TWLG language = TWLG.ENGLISH_USA, TWCY country = TWCY.USA) + _threadMarshaller = threadMarshaller ?? new ThreadPoolMarshaller(); + _hWnd = hWnd; + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) { - var info = FileVersionInfo.GetVersionInfo(application.Location); + if (_twain != null) + { + Close(); + _twain.Dispose(); + _twain = null; + } + Log.Close(); + } + _disposed = true; + } + } - _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); + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } - _threadMarshaller = threadMarshaller ?? new ThreadPoolMarshaller(); - _hWnd = hWnd; + /// + /// Gets the low-level twain object. + /// Only use it if you know what you're doing. + /// + public TWAIN TWAIN { get { return _twain; } } + + #region event callbacks + + /// + /// Raised when data source has encountered some hardwar event. + /// + public event EventHandler DeviceEvent; + + /// + /// Raised when data source comes down to state 4 from higher. + /// + public event EventHandler SourceDisabled; + + /// + /// Raised when there's some error during transfer. + /// + public event EventHandler TransferError; + + /// + /// Raised when there's a pending transfer. Can be used to cancel transfers. + /// + public event EventHandler TransferReady; + + private void HandleUIThreadAction(Action action) + { + DebugThreadInfo("begin"); + + _threadMarshaller.Invoke(action); + } + + private STS HandleDeviceEvent() + { + STS sts; + TW_DEVICEEVENT twdeviceevent; + + // Drain the event queue... + while (true) + { + DebugThreadInfo("in loop"); + + // Try to get an event... + twdeviceevent = default; + sts = _twain.DatDeviceevent(DG.CONTROL, MSG.GET, ref twdeviceevent); + if (sts != STS.SUCCESS) + { + break; + } + else + { + try + { + DeviceEvent?.Invoke(this, twdeviceevent); + } + catch { } + } + } + + // Return a status, in case we ever need it for anything... + return STS.SUCCESS; + } + + private STS HandleScanEvent(bool closing) + { + DebugThreadInfo("begin"); + + // the scan event needs to return asap since it can come from msg loop + // so fire off the handling work to another thread + _threadMarshaller.BeginInvoke(new Action(HandleScanEventReal), closing); + return STS.SUCCESS; + } + + void HandleScanEventReal(bool closing) + { + DebugThreadInfo("begin"); + + if (_twain == null || State <= STATE.S4 || closing) return; + + if (_twain.IsMsgCloseDsReq() || _twain.IsMsgCloseDsOk()) + { + _twain.Rollback(STATE.S4); + return; + } + + // all except mem xfer will run this once and raise event. + // mem xfer will run this multiple times until complete image is assembled + if (_twain.IsMsgXferReady()) + { + TW_PENDINGXFERS pending = default; + var sts = _twain.DatPendingxfers(DG.CONTROL, MSG.GET, ref pending); + if (sts != STS.SUCCESS) + { + try + { + TransferError?.Invoke(this, new TransferErrorEventArgs(sts)); + } + catch { } + return; // do more? } - protected virtual void Dispose(bool disposing) + var xferMech = Capabilities.ICAP_XFERMECH.GetCurrent(); + + var readyArgs = new TransferReadyEventArgs(_twain, pending.Count, (TWEJ)pending.EOJ); + try { - if (!_disposed) + TransferReady?.Invoke(this, readyArgs); + } + catch { } + + if (readyArgs.CancelCapture == CancelType.Immediate) + { + sts = _twain.DatPendingxfers(DG.CONTROL, MSG.RESET, ref pending); + } + else + { + if (readyArgs.CancelCapture == CancelType.Graceful) StopCapture(); + + if (!readyArgs.SkipCurrent) + { + switch (xferMech) { - if (disposing) - { - if (_twain != null) - { - Close(); - _twain.Dispose(); - _twain = null; - } - Log.Close(); - } - _disposed = true; + case TWSX.NATIVE: + RunImageNativeXfer(); + break; + case TWSX.MEMFILE: + RunImageMemFileXfer(); + break; + case TWSX.FILE: + RunImageFileXfer(); + break; + case TWSX.MEMORY: + RunImageMemoryXfer(); + break; } + } + sts = _twain.DatPendingxfers(DG.CONTROL, MSG.ENDXFER, ref pending); } - public void Dispose() + // TODO: may be wrong for now + if (pending.Count == 0 || sts == STS.CANCEL || sts == STS.XFERDONE) { - Dispose(disposing: true); - GC.SuppressFinalize(this); + _twain.Rollback(STATE.S4); } - - /// - /// Gets the low-level twain object. - /// Only use if you know what you're doing. - /// - public TWAIN TWAIN { get { return _twain; } } - - #region event callbacks - - /// - /// Raised when data source has encountered some hardwar event. - /// - public event EventHandler DeviceEvent; - - /// - /// Raised when data source comes down to state 4 from higher. - /// - public event EventHandler SourceDisabled; - - /// - /// Raised when there's some error during transfer. - /// - public event EventHandler TransferError; - - /// - /// Raised when there's a pending transfer. Can be used to cancel transfers. - /// - public event EventHandler TransferReady; - - private void HandleUIThreadAction(Action action) + else { - DebugThreadInfo("begin"); - - _threadMarshaller.Invoke(action); + HandleScanEvent(State <= STATE.S3); } + } - private STS HandleDeviceEvent() + } + + [Conditional("DEBUG")] + private void DebugThreadInfo(string description, [CallerMemberName] string callerName = "") + { + var tid = Thread.CurrentThread.ManagedThreadId; + Debug.WriteLine($"[Thread {tid}] {callerName}() {description}"); + } + + private void RunImageMemoryXfer() + { + throw new NotImplementedException(); + + //// Handle DAT_NULL/MSG_XFERREADY... + //if (_twain.IsMsgXferReady() && !_xferReadySent) + //{ + // _xferReadySent = true; + + // // Get the amount of memory needed... + // TW_SETUPMEMXFER m_twsetupmemxfer = default; + // var sts = _twain.DatSetupmemxfer(DG.CONTROL, MSG.GET, ref m_twsetupmemxfer); + // if ((sts != STS.SUCCESS) || (m_twsetupmemxfer.Preferred == 0)) + // { + // _xferReadySent = false; + // if (!_disableDsSent) + // { + // _disableDsSent = true; + // StepDown(STATE.S4); + // } + // } + + // // Allocate the transfer memory (with a little extra to protect ourselves)... + // var m_intptrXfer = Marshal.AllocHGlobal((int)m_twsetupmemxfer.Preferred + 65536); + // if (m_intptrXfer == IntPtr.Zero) + // { + // _disableDsSent = true; + // StepDown(STATE.S4); + // } + //} + + //// This is where the statemachine runs that transfers and optionally + //// saves the images to disk (it also displays them). It'll go back + //// and forth between states 6 and 7 until an error occurs, or until + //// we run out of images... + //if (_xferReadySent && !_disableDsSent) + //{ + // CaptureImages(); + //} + } + + private void RunImageFileXfer() + { + throw new NotImplementedException(); + } + + private void RunImageMemFileXfer() + { + throw new NotImplementedException(); + } + + private void RunImageNativeXfer() + { + + } + + //protected virtual void OnScanEvent(bool closing) { } + //public event EventHandler ScanEvent; + + #endregion + + #region TWAIN operations + + + /// + /// Gets the current TWAIN state. + /// + public STATE State + { + get { return _twain.GetState(); } + } + + /// + /// Opens the TWAIN data source manager. + /// This needs to be done before anything else. + /// + /// + public STS Open() + { + var sts = _twain.DatParent(DG.CONTROL, MSG.OPENDSM, ref _hWnd); + return sts; + } + + /// + /// Closes the TWAIN data source manager. + /// This is called when is invoked. + /// + public void Close() + { + _twain.Rollback(STATE.S2); + } + + /// + /// Gets list of TWAIN data sources. + /// + /// + public IList GetDataSources() + { + var list = new List(); + if (State > STATE.S2) + { + TW_IDENTITY twidentity = default; + STS sts; + + for (sts = _twain.DatIdentity(DG.CONTROL, MSG.GETFIRST, ref twidentity); + sts != STS.ENDOFLIST; + sts = _twain.DatIdentity(DG.CONTROL, MSG.GETNEXT, ref twidentity)) { - STS sts; - TW_DEVICEEVENT twdeviceevent; - - // Drain the event queue... - while (true) - { - DebugThreadInfo("in loop"); - - // Try to get an event... - twdeviceevent = default; - sts = _twain.DatDeviceevent(DG.CONTROL, MSG.GET, ref twdeviceevent); - if (sts != STS.SUCCESS) - { - break; - } - else - { - try - { - DeviceEvent?.Invoke(this, twdeviceevent); - } - catch { } - } - } - - // Return a status, in case we ever need it for anything... - return STS.SUCCESS; + list.Add(twidentity); } + } + return list; + } - private STS HandleScanEvent(bool closing) + /// + /// Gets or sets the default data source. + /// + public TW_IDENTITY? DefaultDataSource + { + get + { + TW_IDENTITY twidentity = default; + 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) { - DebugThreadInfo("begin"); - - // the scan event needs to return asap since it can come from msg loop - // so fire off the handling work to another thread - _threadMarshaller.BeginInvoke(new Action(HandleScanEventReal), closing); - return STS.SUCCESS; + var twidentity = value.Value; + _twain.DatIdentity(DG.CONTROL, MSG.SET, ref twidentity); } + } + } - void HandleScanEventReal(bool closing) + /// + /// Gets or sets the currently open data source. + /// Setting it will try to open it. + /// + public TW_IDENTITY? CurrentDataSource + { + get + { + if (State > STATE.S3) { - DebugThreadInfo("begin"); - - if (_twain == null || State <= STATE.S4 || closing) return; - - if (_twain.IsMsgCloseDsReq() || _twain.IsMsgCloseDsOk()) - { - StepDown(STATE.S4); - return; - } - - // all except mem xfer will run this once and raise event. - // mem xfer will run this multiple times until complete image is assembled - if (_twain.IsMsgXferReady()) - { - TW_PENDINGXFERS pending = default; - var sts = _twain.DatPendingxfers(DG.CONTROL, MSG.GET, ref pending); - if (sts != STS.SUCCESS) - { - try - { - TransferError?.Invoke(this, new TransferErrorEventArgs(sts)); - } - catch { } - return; // do more? - } - - var xferMech = Capabilities.ICAP_XFERMECH.GetCurrent(); - - var readyArgs = new TransferReadyEventArgs(_twain, pending.Count, (TWEJ)pending.EOJ); - try - { - TransferReady?.Invoke(this, readyArgs); - } - catch { } - - if (readyArgs.CancelCapture == CancelType.Immediate) - { - sts = _twain.DatPendingxfers(DG.CONTROL, MSG.RESET, ref pending); - } - else - { - if (readyArgs.CancelCapture == CancelType.Graceful) StopCapture(); - - if (!readyArgs.SkipCurrent) - { - switch (xferMech) - { - case TWSX.NATIVE: - RunImageNativeXfer(); - break; - case TWSX.MEMFILE: - RunImageMemFileXfer(); - break; - case TWSX.FILE: - RunImageFileXfer(); - break; - case TWSX.MEMORY: - RunImageMemoryXfer(); - break; - } - } - sts = _twain.DatPendingxfers(DG.CONTROL, MSG.ENDXFER, ref pending); - } - - // TODO: may be wrong for now - if (pending.Count == 0 || sts == STS.CANCEL || sts == STS.XFERDONE) - { - StepDown(STATE.S4); - } - else - { - HandleScanEvent(State <= STATE.S3); - } - } - + return _twain.m_twidentityDs; } - - [Conditional("DEBUG")] - private void DebugThreadInfo(string description, [CallerMemberName] string callerName = "") + return null; + } + set + { + _twain.Rollback(STATE.S3); + if (value.HasValue) { - var tid = Thread.CurrentThread.ManagedThreadId; - Debug.WriteLine($"[Thread {tid}] {callerName}() {description}"); + var twidentity = value.Value; + _twain.DatIdentity(DG.CONTROL, MSG.OPENDS, ref twidentity); } + } + } - private void RunImageMemoryXfer() + + private Capabilities _caps; + + /// + /// Get current data source's capabilities. Will be null if no data source is open. + /// + /// + public Capabilities Capabilities + { + get + { + if (State >= STATE.S4) { - throw new NotImplementedException(); - - //// Handle DAT_NULL/MSG_XFERREADY... - //if (_twain.IsMsgXferReady() && !_xferReadySent) - //{ - // _xferReadySent = true; - - // // Get the amount of memory needed... - // TW_SETUPMEMXFER m_twsetupmemxfer = default; - // var sts = _twain.DatSetupmemxfer(DG.CONTROL, MSG.GET, ref m_twsetupmemxfer); - // if ((sts != STS.SUCCESS) || (m_twsetupmemxfer.Preferred == 0)) - // { - // _xferReadySent = false; - // if (!_disableDsSent) - // { - // _disableDsSent = true; - // StepDown(STATE.S4); - // } - // } - - // // Allocate the transfer memory (with a little extra to protect ourselves)... - // var m_intptrXfer = Marshal.AllocHGlobal((int)m_twsetupmemxfer.Preferred + 65536); - // if (m_intptrXfer == IntPtr.Zero) - // { - // _disableDsSent = true; - // StepDown(STATE.S4); - // } - //} - - //// This is where the statemachine runs that transfers and optionally - //// saves the images to disk (it also displays them). It'll go back - //// and forth between states 6 and 7 until an error occurs, or until - //// we run out of images... - //if (_xferReadySent && !_disableDsSent) - //{ - // CaptureImages(); - //} + return _caps ?? (_caps = new Capabilities(_twain)); } + return null; + } + } - private void RunImageFileXfer() + /// + /// Gets/sets the current source's settings as opaque data. + /// Returns null if not supported. + /// + public byte[] CustomDsData + { + get + { + TW_CUSTOMDSDATA data = default; + var sts = _twain.DatCustomdsdata(DG.CONTROL, MSG.GET, ref data); + if (sts == STS.SUCCESS) { - throw new NotImplementedException(); - } - - private void RunImageMemFileXfer() - { - throw new NotImplementedException(); - } - - private void RunImageNativeXfer() - { - - } - - //protected virtual void OnScanEvent(bool closing) { } - //public event EventHandler ScanEvent; - - #endregion - - #region TWAIN operations - - - /// - /// Gets the current TWAIN state. - /// - public STATE State - { - get { return _twain.GetState(); } - } - - /// - /// Opens the TWAIN data source manager. - /// This needs to be done before anything else. - /// - /// - public STS Open() - { - var sts = _twain.DatParent(DG.CONTROL, MSG.OPENDSM, ref _hWnd); - return sts; - } - - /// - /// Closes the TWAIN data source manager. - /// This is called when is invoked. - /// - public void Close() - { - StepDown(STATE.S2); - } - - /// - /// Gets list of TWAIN data sources. - /// - /// - public IList GetDataSources() - { - var list = new List(); - if (State > STATE.S2) - { - TW_IDENTITY twidentity = default; - 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; - } - - /// - /// Gets or sets the default data source. - /// - public TW_IDENTITY? DefaultDataSource - { - get - { - TW_IDENTITY twidentity = default; - 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); - } - } - } - - /// - /// Gets or sets the currently open data source. - /// Setting it will try to open it. - /// - public TW_IDENTITY? CurrentDataSource - { - get - { - if (State > STATE.S3) - { - return _twain.m_twidentityDs; - } - return null; - } - set - { - StepDown(STATE.S3); - if (value.HasValue) - { - var twidentity = value.Value; - _twain.DatIdentity(DG.CONTROL, MSG.OPENDS, ref twidentity); - } - } - } - - /// - /// Steps down the TWAIN state to the specified state. - /// - /// - public void StepDown(STATE target) - { - // 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 ((State == STATE.S7) && (target < STATE.S7)) - { - TW_PENDINGXFERS twpendingxfers = default; - _twain.DatPendingxfers(DG.CONTROL, MSG.ENDXFER, ref twpendingxfers); - } - - // 6 --> 5 - if ((State == STATE.S6) && (target < STATE.S6)) - { - TW_PENDINGXFERS twpendingxfers = default; - _twain.DatPendingxfers(DG.CONTROL, MSG.RESET, ref twpendingxfers); - } - - // 5 --> 4 - if ((State == STATE.S5) && (target < STATE.S5)) - { - TW_USERINTERFACE twuserinterface = default; - _twain.DatUserinterface(DG.CONTROL, MSG.DISABLEDS, ref twuserinterface); - SourceDisabled?.Invoke(this, EventArgs.Empty); - } - - // 4 --> 3 - if ((State == STATE.S4) && (target < STATE.S4)) - { - _caps = null; - _twain.DatIdentity(DG.CONTROL, MSG.CLOSEDS, ref _twain.m_twidentityDs); - } - - // 3 --> 2 - if ((State == STATE.S3) && (target < STATE.S3)) - { - _twain.DatParent(DG.CONTROL, MSG.CLOSEDSM, ref _hWnd); - } - } - - private Capabilities _caps; - - /// - /// Get current data source's capabilities. Will be null if no data source is open. - /// - /// - public Capabilities Capabilities - { - get - { - if (State >= STATE.S4) - { - return _caps ?? (_caps = new Capabilities(_twain)); - } - return null; - } - } - - /// - /// Gets/sets the current source's settings as opaque data. - /// Returns null if not supported. - /// - public byte[] CustomDsData - { - get - { - TW_CUSTOMDSDATA data = default; - var sts = _twain.DatCustomdsdata(DG.CONTROL, MSG.GET, ref data); - if (sts == STS.SUCCESS) - { - if (data.hData != IntPtr.Zero && data.InfoLength > 0) - { - try - { - var lockedPtr = _twain.DsmMemLock(data.hData); - var bytes = new byte[data.InfoLength]; - Marshal.Copy(lockedPtr, bytes, 0, bytes.Length); - } - finally - { - _twain.DsmMemUnlock(data.hData); - _twain.DsmMemFree(ref data.hData); - } - } - return EmptyArray.Value; - } - return null; - } - set - { - if (value == null || value.Length == 0) return; - - TW_CUSTOMDSDATA data = default; - data.InfoLength = (uint)value.Length; - data.hData = _twain.DsmMemAlloc(data.InfoLength); - try - { - var lockedPtr = _twain.DsmMemLock(data.hData); - Marshal.Copy(value, 0, lockedPtr, value.Length); - _twain.DsmMemUnlock(data.hData); - var sts = _twain.DatCustomdsdata(DG.CONTROL, MSG.SET, ref data); - } - finally - { - // should be freed already if no error but just in case - if (data.hData != IntPtr.Zero) _twain.DsmMemFree(ref data.hData); - } - } - } - - - /// - /// Attempts to show the current data source's settings dialog if supported. - /// - /// - public STS ShowSettings() - { - TW_USERINTERFACE ui = default; - ui.hParent = _hWnd; - ui.ShowUI = 1; - return _twain.DatUserinterface(DG.CONTROL, MSG.ENABLEDSUIONLY, ref ui); - } - - /// - /// Begins the capture process on the current data source. - /// - /// Whether to display settings UI. Not all data sources support this. - /// - public STS StartCapture(bool showUI) - { - TW_USERINTERFACE ui = default; - ui.hParent = _hWnd; - ui.ShowUI = (ushort)(showUI ? 1 : 0); - return _twain.DatUserinterface(DG.CONTROL, MSG.ENABLEDS, ref ui); - } - - /// - /// Stops the data source's automated feeder - /// if is set to true. - /// - /// - public STS StopCapture() - { - TW_PENDINGXFERS pending = default; - return _twain.DatPendingxfers(DG.CONTROL, MSG.STOPFEEDER, ref pending); - } - - /// - /// Reads information relating to the last capture run. - /// Only valid on state 4 after a capture. - /// - public Metrics GetMetrics() - { - TW_METRICS twmetrics = default; - twmetrics.SizeOf = (uint)Marshal.SizeOf(twmetrics); - var sts = _twain.DatMetrics(DG.CONTROL, MSG.GET, ref twmetrics); - if (sts == STS.SUCCESS) - { - return new Metrics - { - ReturnCode = sts, - Images = (int)twmetrics.ImageCount, - Sheets = (int)twmetrics.SheetCount - }; - } - return new Metrics { ReturnCode = sts }; - } - - /// - /// Sends a TWAIN Direct task from the application to the driver. - /// - /// The TWAIN Direct task in JSON. - /// The current system being used to connect the application to the scanner. - /// - public TwainDirectTaskResult SetTwainDirectTask(string taskJson, ushort communicationManager = 0) - { - var result = new TwainDirectTaskResult { ReturnCode = STS.FAILURE }; - TW_TWAINDIRECT task = default; + if (data.hData != IntPtr.Zero && data.InfoLength > 0) + { try { - task.SizeOf = (uint)Marshal.SizeOf(typeof(TW_TWAINDIRECT)); - task.CommunicationManager = communicationManager; - task.Send = ValueWriter.StringToPtrUTF8(_twain, taskJson, out int length); - task.SendSize = (uint)length; - - result.ReturnCode = _twain.DatTwaindirect(DG.CONTROL, MSG.SETTASK, ref task); - if (result.ReturnCode == STS.SUCCESS && task.ReceiveSize > 0 && task.Receive != IntPtr.Zero) - { - result.ResponseJson = ValueReader.PtrToStringUTF8(task.Receive, (int)task.ReceiveSize); - } + var lockedPtr = _twain.DsmMemLock(data.hData); + var bytes = new byte[data.InfoLength]; + Marshal.Copy(lockedPtr, bytes, 0, bytes.Length); } finally { - if (task.Send != IntPtr.Zero) _twain.DsmMemFree(ref task.Send); // just in case - if (task.Receive != IntPtr.Zero) _twain.DsmMemFree(ref task.Receive); + _twain.DsmMemUnlock(data.hData); + _twain.DsmMemFree(ref data.hData); } - return result; + } + return EmptyArray.Value; } + return null; + } + set + { + if (value == null || value.Length == 0) return; - #endregion + TW_CUSTOMDSDATA data = default; + data.InfoLength = (uint)value.Length; + data.hData = _twain.DsmMemAlloc(data.InfoLength); + try + { + var lockedPtr = _twain.DsmMemLock(data.hData); + Marshal.Copy(value, 0, lockedPtr, value.Length); + _twain.DsmMemUnlock(data.hData); + var sts = _twain.DatCustomdsdata(DG.CONTROL, MSG.SET, ref data); + } + finally + { + // should be freed already if no error but just in case + if (data.hData != IntPtr.Zero) _twain.DsmMemFree(ref data.hData); + } + } } + + + /// + /// Attempts to show the current data source's settings dialog if supported. + /// + /// + public STS ShowSettings() + { + TW_USERINTERFACE ui = default; + ui.hParent = _hWnd; + ui.ShowUI = 1; + return _twain.DatUserinterface(DG.CONTROL, MSG.ENABLEDSUIONLY, ref ui); + } + + /// + /// Begins the capture process on the current data source. + /// + /// Whether to display settings UI. Not all data sources support this. + /// + public STS StartCapture(bool showUI) + { + TW_USERINTERFACE ui = default; + ui.hParent = _hWnd; + ui.ShowUI = (ushort)(showUI ? 1 : 0); + return _twain.DatUserinterface(DG.CONTROL, MSG.ENABLEDS, ref ui); + } + + /// + /// Stops the data source's automated feeder + /// if is set to true. + /// + /// + public STS StopCapture() + { + TW_PENDINGXFERS pending = default; + return _twain.DatPendingxfers(DG.CONTROL, MSG.STOPFEEDER, ref pending); + } + + /// + /// Reads information relating to the last capture run. + /// Only valid on state 4 after a capture. + /// + public Metrics GetMetrics() + { + TW_METRICS twmetrics = default; + twmetrics.SizeOf = (uint)Marshal.SizeOf(twmetrics); + var sts = _twain.DatMetrics(DG.CONTROL, MSG.GET, ref twmetrics); + if (sts == STS.SUCCESS) + { + return new Metrics + { + ReturnCode = sts, + Images = (int)twmetrics.ImageCount, + Sheets = (int)twmetrics.SheetCount + }; + } + return new Metrics { ReturnCode = sts }; + } + + /// + /// Sends a TWAIN Direct task from the application to the driver. + /// + /// The TWAIN Direct task in JSON. + /// The current system being used to connect the application to the scanner. + /// + public TwainDirectTaskResult SetTwainDirectTask(string taskJson, ushort communicationManager = 0) + { + var result = new TwainDirectTaskResult { ReturnCode = STS.FAILURE }; + TW_TWAINDIRECT task = default; + try + { + task.SizeOf = (uint)Marshal.SizeOf(typeof(TW_TWAINDIRECT)); + task.CommunicationManager = communicationManager; + task.Send = ValueWriter.StringToPtrUTF8(_twain, taskJson, out int length); + task.SendSize = (uint)length; + + result.ReturnCode = _twain.DatTwaindirect(DG.CONTROL, MSG.SETTASK, ref task); + if (result.ReturnCode == STS.SUCCESS && task.ReceiveSize > 0 && task.Receive != IntPtr.Zero) + { + result.ResponseJson = ValueReader.PtrToStringUTF8(task.Receive, (int)task.ReceiveSize); + } + } + finally + { + if (task.Send != IntPtr.Zero) _twain.DsmMemFree(ref task.Send); // just in case + if (task.Receive != IntPtr.Zero) _twain.DsmMemFree(ref task.Receive); + } + return result; + } + + #endregion + } }