From 8949b368332a69ef4a1fd42a229ca15e788cc496 Mon Sep 17 00:00:00 2001 From: Eugene Wang <8755753+soukoku@users.noreply.github.com> Date: Thu, 20 Nov 2025 13:18:21 -0500 Subject: [PATCH] Changed to return value container for available cap value to prevent long range enumerations. --- src/Directory.Build.props | 2 +- src/NTwain/Caps/CapReader.cs | 170 +++++++++++++-------------- src/NTwain/Caps/ValueContainer.cs | 66 +++++++++++ src/NTwain/Data/DynamicEnumerator.cs | 48 ++++++++ src/NTwain/Data/TWAINH.cs | 4 +- src/NTwain/Data/TWAINH_EXTRAS.cs | 47 +------- src/NTwain/TwainAppSession.Caps.cs | 39 ++++-- 7 files changed, 229 insertions(+), 147 deletions(-) create mode 100644 src/NTwain/Caps/ValueContainer.cs create mode 100644 src/NTwain/Data/DynamicEnumerator.cs diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 5e960fe..fa80e3a 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,7 +2,7 @@ 4.0.0.0 - alpha.16 + alpha.17 4.0.0.0 diff --git a/src/NTwain/Caps/CapReader.cs b/src/NTwain/Caps/CapReader.cs index 0f32bd5..f487d48 100644 --- a/src/NTwain/Caps/CapReader.cs +++ b/src/NTwain/Caps/CapReader.cs @@ -4,101 +4,97 @@ using System.Collections.Generic; namespace NTwain.Caps { - /// - /// - /// - /// - public class CapReader where TValue : struct - { - protected readonly TwainAppSession _twain; - - public CapReader(TwainAppSession twain, CAP cap, float introducedVersion = 1) - { - _twain = twain; - Cap = cap; - Introduced = introducedVersion; - } - - public CAP Cap { get; } - /// - /// When this was introduced in TWAIN. + /// /// - public float Introduced { get; } - - /// - /// The STS result from the most recent call with this cap wrapper. - /// - public STS LastSTS { get; protected set; } - - TWQC? _qc; - public TWQC Supports + /// + public class CapReader where TValue : struct { - get - { - if (!_qc.HasValue) _qc = _twain.QueryCapSupport(Cap); - return _qc.Value; - } - } + protected readonly TwainAppSession _twain; - public IList Get() - { - LastSTS = _twain.GetCapValues(Cap, out IList values); - if (LastSTS.IsSuccess) - { - return values; - }; - return Array.Empty(); - } + public CapReader(TwainAppSession twain, CAP cap, float introducedVersion = 1) + { + _twain = twain; + Cap = cap; + Introduced = introducedVersion; + } - public IList GetCurrent() - { - LastSTS = _twain.GetCapCurrent(Cap, out List value); - if (LastSTS.IsSuccess) - { - return value; - }; - return Array.Empty(); - } + public CAP Cap { get; } - public IList GetDefault() - { - LastSTS = _twain.GetCapDefault(Cap, out List value); - if (LastSTS.IsSuccess) - { - return value; - }; - return Array.Empty(); - } + /// + /// When this was introduced in TWAIN. + /// + public float Introduced { get; } - public string? GetLabel() - { - LastSTS = _twain.GetCapLabel(Cap, out string? value); - if (LastSTS.IsSuccess) - { - return value; - }; - return default; - } + /// + /// The STS result from the most recent call with this cap wrapper. + /// + public STS LastSTS { get; protected set; } - public string? GetHelp() - { - LastSTS = _twain.GetCapHelp(Cap, out string? value); - if (LastSTS.IsSuccess) - { - return value; - }; - return default; - } + TWQC? _qc; + public TWQC Supports + { + get + { + if (!_qc.HasValue) _qc = _twain.QueryCapSupport(Cap); + return _qc.Value; + } + } - public IList GetLabelEnum() - { - LastSTS = _twain.GetCapLabelEnum(Cap, out IList value); - if (LastSTS.IsSuccess) - { - return value; - }; - return Array.Empty(); + public ValueContainer Get() + { + LastSTS = _twain.GetCapValues(Cap, out ValueContainer value); + return value; + } + + public IList GetCurrent() + { + LastSTS = _twain.GetCapCurrent(Cap, out List value); + if (LastSTS.IsSuccess) + { + return value; + } + return Array.Empty(); + } + + public IList GetDefault() + { + LastSTS = _twain.GetCapDefault(Cap, out List value); + if (LastSTS.IsSuccess) + { + return value; + } + return Array.Empty(); + } + + public string? GetLabel() + { + LastSTS = _twain.GetCapLabel(Cap, out string? value); + if (LastSTS.IsSuccess) + { + return value; + } + return default; + } + + public string? GetHelp() + { + LastSTS = _twain.GetCapHelp(Cap, out string? value); + if (LastSTS.IsSuccess) + { + return value; + } + return default; + } + + public IList GetLabelEnum() + { + LastSTS = _twain.GetCapLabelEnum(Cap, out IList value); + if (LastSTS.IsSuccess) + { + return value; + } + return Array.Empty(); + } } - } } diff --git a/src/NTwain/Caps/ValueContainer.cs b/src/NTwain/Caps/ValueContainer.cs new file mode 100644 index 0000000..238adcc --- /dev/null +++ b/src/NTwain/Caps/ValueContainer.cs @@ -0,0 +1,66 @@ +using NTwain.Data; +using System.Collections.Generic; +using System.Linq; + +namespace NTwain.Caps; + +public record ValueContainer where TValue : struct +{ + public TWON ContainerType { get; set; } + + public TValue? OneValue { get; set; } + + public IList? ArrayValue { get; set; } + + public EnumValue? EnumValue { get; set; } + + public RangeValue? RangeValue { get; set; } + + public IEnumerable GetValues() + { + return ContainerType switch + { + TWON.ONEVALUE => OneValue.HasValue ? ToEnumerable(OneValue.Value) : [], + TWON.ARRAY => ArrayValue ?? [], + TWON.ENUMERATION => EnumValue?.Items ?? [], + TWON.RANGE => RangeValue != null ? GenerateRangeValues(RangeValue) : [], + _ => [], + }; + } + + private IEnumerable ToEnumerable(TValue value) + { + yield return value; + } + + private IEnumerable GenerateRangeValues(RangeValue range) + { + var de = new DynamicEnumerator(range.Min, range.Max, range.Step); + while (de.MoveNext()) + { + yield return de.Current; + } + } +} + +public record EnumValue where TValue : struct +{ + public TValue[] Items { get; set; } = []; + + public int CurrentIndex { get; set; } + + public int DefaultIndex { get; set; } +} + +public record RangeValue where TValue : struct +{ + public TValue Min { get; set; } + + public TValue Max { get; set; } + + public TValue Step { get; set; } + + public TValue DefaultValue; + + public TValue CurrentValue; +} diff --git a/src/NTwain/Data/DynamicEnumerator.cs b/src/NTwain/Data/DynamicEnumerator.cs new file mode 100644 index 0000000..524a6b7 --- /dev/null +++ b/src/NTwain/Data/DynamicEnumerator.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; + +namespace NTwain.Data; + +// dynamic is a cheap hack to sidestep the compiler restrictions if I know TValue is numeric +class DynamicEnumerator : IEnumerator where TValue : struct +{ + 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 System.Collections.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; + } +} diff --git a/src/NTwain/Data/TWAINH.cs b/src/NTwain/Data/TWAINH.cs index 5563792..99fe53e 100644 --- a/src/NTwain/Data/TWAINH.cs +++ b/src/NTwain/Data/TWAINH.cs @@ -2590,7 +2590,9 @@ namespace NTwain.Data ICONID = 962, DSMID = 461, - DSMCODEID = 63 + DSMCODEID = 63, + + DONTCARE = 0xffff } ///// diff --git a/src/NTwain/Data/TWAINH_EXTRAS.cs b/src/NTwain/Data/TWAINH_EXTRAS.cs index 0be612e..aadcbc4 100644 --- a/src/NTwain/Data/TWAINH_EXTRAS.cs +++ b/src/NTwain/Data/TWAINH_EXTRAS.cs @@ -284,58 +284,13 @@ namespace NTwain.Data if (MinValue is not IConvertible) throw new NotSupportedException($"The value type {typeof(TValue).Name} is not supported for range enumeration."); - return new DynamicEnumerator(MinValue, MaxValue, StepSize); + return new DynamicEnumerator(MinValue, MaxValue, StepSize); } System.Collections.IEnumerator System.Collections.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 System.Collections.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 struct TW_FIX32 : IEquatable, IConvertible diff --git a/src/NTwain/TwainAppSession.Caps.cs b/src/NTwain/TwainAppSession.Caps.cs index 494f805..acc011f 100644 --- a/src/NTwain/TwainAppSession.Caps.cs +++ b/src/NTwain/TwainAppSession.Caps.cs @@ -1,7 +1,6 @@ using NTwain.Caps; using NTwain.Data; using NTwain.Triplets; -using NTwain.Triplets.ControlDATs; using System; using System.Collections.Generic; using System.Linq; @@ -178,31 +177,47 @@ namespace NTwain /// /// /// - public STS GetCapValues(CAP cap, out IList values) where TValue : struct + public STS GetCapValues(CAP cap, out ValueContainer value) where TValue : struct { - values = new List(); - var sts = GetCapValues(cap, out TW_CAPABILITY twcap); + value = new ValueContainer { ContainerType = TWON.DONTCARE }; + var sts = GetCapCurrent(cap, out TW_CAPABILITY twcap); if (sts.RC == TWRC.SUCCESS) { + value.ContainerType = twcap.ConType; switch (twcap.ConType) { case TWON.ONEVALUE: - values.Add(twcap.ReadOneValue(this)); + value.OneValue = twcap.ReadOneValue(this); break; case TWON.ENUMERATION: var twenum = twcap.ReadEnumeration(this); - if (twenum.Items != null && twenum.Items.Length > 0) - ((List)values).AddRange(twenum.Items); + if (twenum.Items != null) + { + value.EnumValue = new EnumValue + { + CurrentIndex = twenum.CurrentIndex, + DefaultIndex = twenum.DefaultIndex, + Items = twenum.Items + }; + } break; case TWON.RANGE: - // This can be slow - var twrange = twcap.ReadRange(this); - ((List)values).AddRange(twrange); + var range = twcap.ReadRange(this); + value.RangeValue = new RangeValue + { + Min = range.MinValue, + Max = range.MaxValue, + Step = range.StepSize, + DefaultValue = range.DefaultValue, + CurrentValue = range.CurrentValue + }; break; case TWON.ARRAY: var twarr = twcap.ReadArray(this); - if (twarr != null && twarr.Count > 0) - ((List)values).AddRange(twarr); + if (twarr != null) + { + value.ArrayValue = twarr; + } break; default: twcap.Free(this); break;