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
+ }
}