Merge pull request #76 from soukoku/BoxedExperiments

Boxed experiments
This commit is contained in:
Eugene Wang
2025-12-12 08:02:14 -05:00
committed by GitHub
9 changed files with 1496 additions and 877 deletions

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<!--change these in each release-->
<VersionPrefix>4.0.0.0</VersionPrefix>
<VersionSuffix>alpha.18</VersionSuffix>
<VersionSuffix>alpha.22</VersionSuffix>
<!--keep it the same until major # changes-->
<AssemblyVersion>4.0.0.0</AssemblyVersion>

View File

@@ -1,10 +1,10 @@
using NTwain.Data;
using System.Collections.Generic;
namespace NTwain.Caps
namespace NTwain.Caps;
public class CapWriter<TValue> : CapReader<TValue> where TValue : struct
{
public class CapWriter<TValue> : CapReader<TValue> where TValue : struct
{
public CapWriter(TwainAppSession twain, CAP cap, float introducedVersion = 1)
: base(twain, cap, introducedVersion)
{
@@ -17,7 +17,7 @@ namespace NTwain.Caps
/// <returns></returns>
public STS Set(TValue value)
{
return LastSTS = _twain.SetCap(Cap, value);
return LastSTS = _twain.SetCap(Cap, value);
}
/// <summary>
@@ -27,8 +27,7 @@ namespace NTwain.Caps
/// <returns></returns>
public STS Set(IList<TValue> values)
{
var twcap = ValueWriter.CreateArrayCap(Cap, _twain, values);
return LastSTS = _twain.SetCap(ref twcap);
return LastSTS = _twain.SetCap(Cap, values);
}
/// <summary>
@@ -38,8 +37,7 @@ namespace NTwain.Caps
/// <returns></returns>
public STS Set(Enumeration<TValue> values)
{
var twcap = ValueWriter.CreateEnumCap(Cap, _twain, values);
return LastSTS = _twain.SetCap(ref twcap);
return LastSTS = _twain.SetCap(Cap, values);
}
/// <summary>
@@ -49,8 +47,7 @@ namespace NTwain.Caps
/// <returns></returns>
public STS Set(Range<TValue> values)
{
var twcap = ValueWriter.CreateRangeCap(Cap, _twain, values);
return LastSTS = _twain.SetCap(ref twcap);
return LastSTS = _twain.SetCap(Cap, values);
}
@@ -61,8 +58,7 @@ namespace NTwain.Caps
/// <returns></returns>
public STS SetConstraint(TValue value)
{
var twcap = ValueWriter.CreateOneValueCap(Cap, _twain, value);
return LastSTS = _twain.SetConstraint(ref twcap);
return LastSTS = _twain.SetConstraint(Cap, value);
}
/// <summary>
@@ -72,8 +68,7 @@ namespace NTwain.Caps
/// <returns></returns>
public STS SetConstraint(IList<TValue> values)
{
var twcap = ValueWriter.CreateArrayCap(Cap, _twain, values);
return LastSTS = _twain.SetConstraint(ref twcap);
return LastSTS = _twain.SetConstraint(Cap, values);
}
/// <summary>
@@ -83,8 +78,7 @@ namespace NTwain.Caps
/// <returns></returns>
public STS SetConstraint(Enumeration<TValue> values)
{
var twcap = ValueWriter.CreateEnumCap(Cap, _twain, values);
return LastSTS = _twain.SetConstraint(ref twcap);
return LastSTS = _twain.SetConstraint(Cap, values);
}
/// <summary>
@@ -94,19 +88,17 @@ namespace NTwain.Caps
/// <returns></returns>
public STS SetConstraint(Range<TValue> values)
{
var twcap = ValueWriter.CreateRangeCap(Cap, _twain, values);
return LastSTS = _twain.SetConstraint(ref twcap);
return LastSTS = _twain.SetConstraint(Cap, values);
}
/// <summary>
/// Resets this cap to power-on default.
/// </summary>
/// <param name="value">The current value after reset.</param>
/// <returns></returns>
public STS Reset()
public STS Reset(out List<TValue> value)
{
LastSTS = _twain.ResetCap(Cap, out TW_CAPABILITY twcap);
twcap.Free(_twain);
return LastSTS;
LastSTS = _twain.ResetCap(Cap, out value);
return LastSTS;
}
}
}

View File

@@ -1,66 +0,0 @@
using NTwain.Data;
using System.Collections.Generic;
using System.Linq;
namespace NTwain.Caps;
public record ValueContainer<TValue> where TValue : struct
{
public TWON ContainerType { get; set; }
public TValue? OneValue { get; set; }
public IList<TValue>? ArrayValue { get; set; }
public EnumValue<TValue>? EnumValue { get; set; }
public RangeValue<TValue>? RangeValue { get; set; }
public IEnumerable<TValue> 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<TValue> ToEnumerable(TValue value)
{
yield return value;
}
private IEnumerable<TValue> GenerateRangeValues(RangeValue<TValue> range)
{
var de = new DynamicEnumerator<TValue>(range.Min, range.Max, range.Step);
while (de.MoveNext())
{
yield return de.Current;
}
}
}
public record EnumValue<TValue> where TValue : struct
{
public TValue[] Items { get; set; } = [];
public int CurrentIndex { get; set; }
public int DefaultIndex { get; set; }
}
public record RangeValue<TValue> where TValue : struct
{
public TValue Min { get; set; }
public TValue Max { get; set; }
public TValue Step { get; set; }
public TValue DefaultValue;
public TValue CurrentValue;
}

View File

@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
namespace System.Runtime.CompilerServices
{
#if !NET5_0_OR_GREATER
[EditorBrowsable(EditorBrowsableState.Never)]
internal static class IsExternalInit {}
#endif // !NET5_0_OR_GREATER
#if !NET7_0_OR_GREATER
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
internal sealed class RequiredMemberAttribute : Attribute {}
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
internal sealed class CompilerFeatureRequiredAttribute : Attribute
{
public CompilerFeatureRequiredAttribute(string featureName)
{
FeatureName = featureName;
}
public string FeatureName { get; }
public bool IsOptional { get; init; }
public const string RefStructs = nameof(RefStructs);
public const string RequiredMembers = nameof(RequiredMembers);
}
#endif // !NET7_0_OR_GREATER
}
namespace System.Diagnostics.CodeAnalysis
{
#if !NET7_0_OR_GREATER
[AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)]
internal sealed class SetsRequiredMembersAttribute : Attribute {}
#endif
}

View File

@@ -1,7 +1,9 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
@@ -258,38 +260,80 @@ namespace NTwain.Data
/// A more dotnet-friendly representation of <see cref="TW_ENUMERATION"/>.
/// </summary>
/// <typeparam name="TValue"></typeparam>
public class Enumeration<TValue> where TValue : struct
public record Enumeration<TValue>
{
public int CurrentIndex;
public int CurrentIndex { get; set; }
public int DefaultIndex;
public int DefaultIndex { get; set; }
public TValue[]? Items;
public TValue[] Items { get; set; } = [];
}
/// <summary>
/// A more dotnet-friendly representation of <see cref="TW_RANGE"/>.
/// </summary>
/// <typeparam name="TValue"></typeparam>
public partial class Range<TValue> : IEnumerable<TValue> where TValue : struct
public partial record Range<TValue>
{
public TValue MinValue;
public TValue MaxValue;
public TValue StepSize;
public TValue DefaultValue;
public TValue CurrentValue;
public required TValue MinValue { get; set; }
public required TValue MaxValue { get; set; }
public required TValue StepSize { get; set; }
public required TValue DefaultValue { get; set; }
public required TValue CurrentValue { get; set; }
IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator()
/// <summary>
/// Tries to enumerate the range values.
/// This could be expensive depending on the range size.
/// </summary>
/// <returns></returns>
public IEnumerable<TValue> Enumerate()
{
if (MinValue is not IConvertible)
throw new NotSupportedException($"The value type {typeof(TValue).Name} is not supported for range enumeration.");
return new DynamicEnumerator<TValue>(MinValue, MaxValue, StepSize);
var dynamicType = typeof(DynamicEnumerator<>);
var genericType = dynamicType.MakeGenericType(typeof(TValue));
var de = (IEnumerator<TValue>)Activator.CreateInstance(genericType, MinValue, MaxValue, StepSize)!;
while (de.MoveNext())
{
yield return de.Current;
}
}
}
/// <summary>
/// A more dotnet-friendly container of CAP value.
/// </summary>
/// <typeparam name="TValue"></typeparam>
public record ValueContainer<TValue>
{
public TWON ContainerType { get; set; }
public TValue? OneValue { get; set; }
public IList<TValue>? ArrayValue { get; set; }
public Enumeration<TValue>? EnumValue { get; set; }
public Range<TValue>? RangeValue { get; set; }
public IEnumerable<TValue> GetValues()
{
return ContainerType switch
{
TWON.ONEVALUE => ToEnumerable(OneValue),
TWON.ARRAY => ArrayValue ?? [],
TWON.ENUMERATION => EnumValue?.Items ?? [],
TWON.RANGE => RangeValue != null ? RangeValue.Enumerate() : [],
_ => [],
};
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
private IEnumerable<TValue> ToEnumerable(TValue? value)
{
return ((IEnumerable<TValue>)this).GetEnumerator();
if (value == null) yield break;
yield return value;
}
}

View File

@@ -84,6 +84,51 @@ namespace NTwain.Data
}
/// <summary>
/// Reads a boxed one value out of a cap. This can only be done once if memory is freed.
/// </summary>
/// <param name="cap"></param>
/// <param name="memMgr"></param>
/// <param name="freeMemory"></param>
/// <returns></returns>
public static object? ReadOneValueBoxed(this ref TW_CAPABILITY cap, IMemoryManager memMgr, bool freeMemory = true)
{
if (cap.ConType != TWON.ONEVALUE || cap.hContainer == IntPtr.Zero) return default;
var lockedPtr = memMgr.Lock(cap.hContainer);
try
{
TWTY itemType;
// Mac has a level of indirection and a different structure (ick)...
if (TWPlatform.IsMacOSX)
{
// Crack the container...
var onevalue = MarshalTo<TW_ONEVALUE_MACOSX>(lockedPtr);
itemType = (TWTY)onevalue.ItemType;
lockedPtr += Marshal.SizeOf(onevalue);
}
else
{
// Crack the container...
var onevalue = MarshalTo<TW_ONEVALUE>(lockedPtr);
itemType = onevalue.ItemType;
lockedPtr += Marshal.SizeOf(onevalue);
}
return ReadTWTYDataBoxed(lockedPtr, itemType, 0);
}
finally
{
if (lockedPtr != IntPtr.Zero) memMgr.Unlock(cap.hContainer);
if (freeMemory)
{
memMgr.Free(cap.hContainer);
cap.hContainer = IntPtr.Zero;
}
}
}
/// <summary>
/// Reads a one value out of a cap. This can only be done once if memory is freed.
/// </summary>
@@ -130,6 +175,78 @@ namespace NTwain.Data
}
}
public static Enumeration<object> ReadEnumerationBoxed(this ref TW_CAPABILITY cap, IMemoryManager memMgr, bool freeMemory = true)
{
Enumeration<object> retVal = new();
if (cap.ConType != TWON.ENUMERATION || cap.hContainer == IntPtr.Zero) return retVal;
var lockedPtr = memMgr.Lock(cap.hContainer);
try
{
TWTY itemType;
int count = 0;
// Mac has a level of indirection and a different structure (ick)...
if (TWPlatform.IsMacOSX)
{
// Crack the container...
var twenumerationmacosx = MarshalTo<TW_ENUMERATION_MACOSX>(lockedPtr);
itemType = (TWTY)twenumerationmacosx.ItemType;
count = (int)twenumerationmacosx.NumItems;
retVal.DefaultIndex = (int)twenumerationmacosx.DefaultIndex;
retVal.CurrentIndex = (int)twenumerationmacosx.CurrentIndex;
lockedPtr += Marshal.SizeOf(twenumerationmacosx);
}
// Windows or the 2.4+ Linux DSM...
else
{
// Crack the container...
var twenumeration = MarshalTo<TW_ENUMERATION>(lockedPtr);
itemType = twenumeration.ItemType;
count = (int)twenumeration.NumItems;
retVal.DefaultIndex = (int)twenumeration.DefaultIndex;
retVal.CurrentIndex = (int)twenumeration.CurrentIndex;
lockedPtr += Marshal.SizeOf(twenumeration);
}
// The -2.3 Linux DSM...
//else if (twain.m_blFound020302Dsm64bit && (twain.m_linuxdsm == TWAIN.LinuxDsm.Is020302Dsm64bit))
//{
// // Crack the container...
// var twenumerationlinux64 = MarshalTo<TW_ENUMERATION_LINUX64>(lockedPtr);
// itemType = twenumerationlinux64.ItemType;
// count = (int)twenumerationlinux64.NumItems;
// retVal.DefaultIndex = (int)twenumerationlinux64.DefaultIndex;
// retVal.CurrentIndex = (int)twenumerationlinux64.CurrentIndex;
// lockedPtr += Marshal.SizeOf(twenumerationlinux64);
//}
// This shouldn't be possible, but what the hey...
//else
//{
// Log.Error("This is serious, you win a cookie for getting here...");
// return retVal;
//}
retVal.Items = new object[count];
for (var i = 0; i < count; i++)
{
retVal.Items[i] = ReadTWTYDataBoxed(lockedPtr, itemType, i);
}
}
finally
{
if (lockedPtr != IntPtr.Zero) memMgr.Unlock(cap.hContainer);
if (freeMemory)
{
memMgr.Free(cap.hContainer);
cap.hContainer = IntPtr.Zero;
}
}
return retVal;
}
public static Enumeration<TValue> ReadEnumeration<TValue>(this ref TW_CAPABILITY cap, IMemoryManager memMgr, bool freeMemory = true) where TValue : struct
{
Enumeration<TValue> retVal = new();
@@ -202,6 +319,53 @@ namespace NTwain.Data
return retVal;
}
public static IList<object> ReadArrayBoxed(this ref TW_CAPABILITY cap, IMemoryManager memMgr, bool freeMemory = true)
{
if (cap.ConType != TWON.ARRAY || cap.hContainer == IntPtr.Zero) return Array.Empty<object>();
var lockedPtr = memMgr.Lock(cap.hContainer);
try
{
TWTY itemType;
uint count;
// Mac has a level of indirection and a different structure (ick)...
if (TWPlatform.IsMacOSX)
{
// Crack the container...
var twarraymacosx = MarshalTo<TW_ARRAY_MACOSX>(lockedPtr);
itemType = (TWTY)twarraymacosx.ItemType;
count = twarraymacosx.NumItems;
lockedPtr += Marshal.SizeOf(twarraymacosx);
}
else
{
// Crack the container...
var twarray = MarshalTo<TW_ARRAY>(lockedPtr);
itemType = twarray.ItemType;
count = twarray.NumItems;
lockedPtr += Marshal.SizeOf(twarray);
}
var arr = new object[count];
for (var i = 0; i < count; i++)
{
arr[i] = ReadTWTYDataBoxed(lockedPtr, itemType, i);
}
return arr;
}
finally
{
if (lockedPtr != IntPtr.Zero) memMgr.Unlock(cap.hContainer);
if (freeMemory)
{
memMgr.Free(cap.hContainer);
cap.hContainer = IntPtr.Zero;
}
}
}
public static IList<TValue> ReadArray<TValue>(this ref TW_CAPABILITY cap, IMemoryManager memMgr, bool freeMemory = true) where TValue : struct
{
if (cap.ConType != TWON.ARRAY || cap.hContainer == IntPtr.Zero) return Array.Empty<TValue>();
@@ -249,11 +413,9 @@ namespace NTwain.Data
}
}
public static Range<TValue> ReadRange<TValue>(this ref TW_CAPABILITY cap, IMemoryManager memMgr, bool freeMemory = true) where TValue : struct
public static Range<object>? ReadRangeBoxed(this ref TW_CAPABILITY cap, IMemoryManager memMgr, bool freeMemory = true)
{
var retVal = new Range<TValue>();
if (cap.ConType != TWON.RANGE || cap.hContainer == IntPtr.Zero) return retVal;
if (cap.ConType != TWON.RANGE || cap.hContainer == IntPtr.Zero) return null;
var lockedPtr = memMgr.Lock(cap.hContainer);
@@ -272,17 +434,75 @@ namespace NTwain.Data
itemType = (TWTY)Marshal.ReadInt16(lockedPtr);
lockedPtr += 2;
}
retVal.MinValue = ReadTWTYData<TValue>(lockedPtr, itemType, 0);
var minValue = ReadTWTYDataBoxed(lockedPtr, itemType, 0);
lockedPtr += 4;
retVal.MaxValue = ReadTWTYData<TValue>(lockedPtr, itemType, 0);
var maxValue = ReadTWTYDataBoxed(lockedPtr, itemType, 0);
lockedPtr += 4;
retVal.StepSize = ReadTWTYData<TValue>(lockedPtr, itemType, 0);
var stepSize = ReadTWTYDataBoxed(lockedPtr, itemType, 0);
lockedPtr += 4;
retVal.CurrentValue = ReadTWTYData<TValue>(lockedPtr, itemType, 0);
var currentValue = ReadTWTYDataBoxed(lockedPtr, itemType, 0);
lockedPtr += 4;
retVal.DefaultValue = ReadTWTYData<TValue>(lockedPtr, itemType, 0);
var defaultValue = ReadTWTYDataBoxed(lockedPtr, itemType, 0);
lockedPtr += 4;
return retVal;
return new Range<object>
{
MinValue = minValue,
MaxValue = maxValue,
StepSize = stepSize,
CurrentValue = currentValue,
DefaultValue = defaultValue
};
}
finally
{
if (lockedPtr != IntPtr.Zero) memMgr.Unlock(cap.hContainer);
if (freeMemory)
{
memMgr.Free(cap.hContainer);
cap.hContainer = IntPtr.Zero;
}
}
}
public static Range<TValue>? ReadRange<TValue>(this ref TW_CAPABILITY cap, IMemoryManager memMgr, bool freeMemory = true) where TValue : struct
{
if (cap.ConType != TWON.RANGE || cap.hContainer == IntPtr.Zero) return null;
var lockedPtr = memMgr.Lock(cap.hContainer);
try
{
TWTY itemType;
// Mac has a level of indirection and a different structure (ick)...
if (TWPlatform.IsMacOSX)
{
itemType = (TWTY)Marshal.ReadInt32(lockedPtr);
lockedPtr += 4;
}
else
{
// Windows or the 2.4+ Linux DSM...
itemType = (TWTY)Marshal.ReadInt16(lockedPtr);
lockedPtr += 2;
}
var minValue = ReadTWTYData<TValue>(lockedPtr, itemType, 0);
lockedPtr += 4;
var maxValue = ReadTWTYData<TValue>(lockedPtr, itemType, 0);
lockedPtr += 4;
var stepSize = ReadTWTYData<TValue>(lockedPtr, itemType, 0);
lockedPtr += 4;
var currentValue = ReadTWTYData<TValue>(lockedPtr, itemType, 0);
lockedPtr += 4;
var defaultValue = ReadTWTYData<TValue>(lockedPtr, itemType, 0);
lockedPtr += 4;
return new Range<TValue>
{
MinValue = minValue,
MaxValue = maxValue,
StepSize = stepSize,
CurrentValue = currentValue,
DefaultValue = defaultValue
};
}
finally
{
@@ -373,6 +593,64 @@ namespace NTwain.Data
}
/// <summary>
/// Read the pointer content as a boxed value specified by <see cref="TWTY"/>, except <see cref="TWTY.HANDLE"/>.
/// </summary>
/// <param name="intptr">A locked pointer to the data pointer. If data is array this is the 0th item.</param>
/// <param name="type">The twain type.</param>
/// <param name="itemIndex">Index of the item if pointer is array.</param>
/// <returns></returns>
public static object ReadTWTYDataBoxed(this IntPtr intptr, TWTY type, int itemIndex)
{
switch (type)
{
default:
throw new NotSupportedException($"Unsupported item type {type} for reading.");
// TODO: verify if needs to read int32 for small types
case TWTY.HANDLE:
intptr += IntPtr.Size * itemIndex;
return MarshalTo<IntPtr>(intptr);
case TWTY.INT8:
intptr += 1 * itemIndex;
return MarshalTo<sbyte>(intptr);
case TWTY.UINT8:
intptr += 1 * itemIndex;
return MarshalTo<byte>(intptr);
case TWTY.INT16:
intptr += 2 * itemIndex;
return MarshalTo<short>(intptr);
case TWTY.BOOL:
case TWTY.UINT16:
intptr += 2 * itemIndex;
return MarshalTo<ushort>(intptr);
case TWTY.INT32:
intptr += 4 * itemIndex;
return MarshalTo<int>(intptr);
case TWTY.UINT32:
intptr += 4 * itemIndex;
return MarshalTo<uint>(intptr);
case TWTY.FIX32:
intptr += 4 * itemIndex;
return MarshalTo<TW_FIX32>(intptr);
case TWTY.FRAME:
intptr += 16 * itemIndex;
return MarshalTo<TW_FRAME>(intptr);
case TWTY.STR32:
intptr += TW_STR32.Size * itemIndex;
return MarshalTo<TW_STR32>(intptr);
case TWTY.STR64:
intptr += TW_STR64.Size * itemIndex;
return MarshalTo<TW_STR64>(intptr);
case TWTY.STR128:
intptr += TW_STR128.Size * itemIndex;
return MarshalTo<TW_STR128>(intptr);
case TWTY.STR255:
intptr += TW_STR255.Size * itemIndex;
return MarshalTo<TW_STR255>(intptr);
}
}
/// <summary>
/// Read the pointer content as a value specified by <see cref="TWTY"/>, except <see cref="TWTY.HANDLE"/>.
/// </summary>

File diff suppressed because it is too large Load Diff

View File

@@ -8,123 +8,134 @@ using System.Windows.Forms;
namespace NTwain
{
/// <summary>
/// For use under Windows to host a message pump.
/// </summary>
class MessagePumpThread
{
DummyForm? _dummyForm;
TwainAppSession? _twain;
public bool IsRunning => _dummyForm != null && _dummyForm.IsHandleCreated;
/// <summary>
/// Starts the thread, attaches a twain session to it,
/// and opens the DSM.
/// For use under Windows to host a message pump.
/// </summary>
/// <param name="twain"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
public async Task<STS> AttachAsync(TwainAppSession twain)
class MessagePumpThread
{
if (twain.State > STATE.S2) throw new InvalidOperationException("Cannot attach to an opened TWAIN session.");
if (_twain != null || _dummyForm != null) throw new InvalidOperationException("Already attached previously.");
DummyForm? _dummyForm;
TwainAppSession? _twain;
Thread t = new(RunMessagePump);
t.IsBackground = true;
t.SetApartmentState(ApartmentState.STA);
t.Start();
public bool IsRunning => _dummyForm != null && _dummyForm.IsHandleCreated;
while (_dummyForm == null || !_dummyForm.IsHandleCreated)
{
await Task.Delay(50);
}
STS sts = default;
TaskCompletionSource<bool> tcs = new();
_dummyForm.BeginInvoke(() =>
{
try
/// <summary>
/// Starts the thread, attaches a twain session to it,
/// and opens the DSM.
/// </summary>
/// <param name="twain"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
public async Task<STS> AttachAsync(TwainAppSession twain)
{
sts = twain.OpenDSM(_dummyForm.Handle, SynchronizationContext.Current!);
if (sts.IsSuccess)
{
twain.AddWinformFilter();
_twain = twain;
}
else
{
_dummyForm.Close();
}
if (twain.State > STATE.S2) throw new InvalidOperationException("Cannot attach to an opened TWAIN session.");
if (_twain != null || _dummyForm != null) throw new InvalidOperationException("Already attached previously.");
Thread t = new(RunMessagePump);
t.IsBackground = true;
t.SetApartmentState(ApartmentState.STA);
t.Start();
while (_dummyForm == null || !_dummyForm.IsHandleCreated)
{
await Task.Delay(50);
}
STS sts = default;
TaskCompletionSource<bool> tcs = new();
_dummyForm.BeginInvoke(() =>
{
try
{
sts = twain.OpenDSM(_dummyForm.Handle, SynchronizationContext.Current!);
if (sts.IsSuccess)
{
twain.AddWinformFilter();
_twain = twain;
}
else
{
_dummyForm.Close();
}
}
finally
{
tcs.TrySetResult(true);
}
});
await tcs.Task;
return sts;
}
finally
/// <summary>
/// Detatches a previously attached session and stops the thread.
/// </summary>
public async Task<STS> DetachAsync()
{
tcs.TrySetResult(true);
STS sts = default;
if (_dummyForm != null && _twain != null)
{
TaskCompletionSource<STS> tcs = new();
_dummyForm.BeginInvoke(() =>
{
sts = _twain.CloseDSMReal();
if (sts.IsSuccess)
{
_twain.RemoveWinformFilter();
_dummyForm.Close();
_twain = null;
}
tcs.SetResult(sts);
});
await tcs.Task;
}
return sts;
}
});
await tcs.Task;
return sts;
}
/// <summary>
/// Detatches a previously attached session and stops the thread.
/// </summary>
public async Task<STS> DetachAsync()
{
STS sts = default;
if (_dummyForm != null && _twain != null)
{
TaskCompletionSource<STS> tcs = new();
_dummyForm.BeginInvoke(() =>
public void BringWindowToFront()
{
sts = _twain.CloseDSMReal();
if (sts.IsSuccess)
{
_twain.RemoveWinformFilter();
_dummyForm.Close();
_twain = null;
}
tcs.SetResult(sts);
});
await tcs.Task;
}
return sts;
}
if (_dummyForm != null)
{
_dummyForm.BeginInvoke(_dummyForm.BringToFront);
}
}
public void BringWindowToFront()
{
if (_dummyForm != null)
{
_dummyForm.BeginInvoke(_dummyForm.BringToFront);
}
}
void RunMessagePump()
{
Debug.WriteLine("TWAIN pump thread starting");
_dummyForm = new DummyForm();
_dummyForm.FormClosed += (s, e) =>
{
_dummyForm = null;
};
Application.Run(_dummyForm);
Debug.WriteLine("TWAIN pump thread ended");
}
void RunMessagePump()
{
Debug.WriteLine("TWAIN pump thread starting");
_dummyForm = new DummyForm();
_dummyForm.FormClosed += (s, e) =>
{
_dummyForm = null;
};
Application.Run(_dummyForm);
Debug.WriteLine("TWAIN pump thread ended");
}
class DummyForm : Form
{
public DummyForm()
{
ShowInTaskbar = false;
WindowState = FormWindowState.Minimized;
Text = "NTwain Internal Loop";
}
class DummyForm : Form
{
public DummyForm()
{
ShowInTaskbar = false;
WindowState = FormWindowState.Minimized;
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x80; // WS_EX_TOOLWINDOW
return cp;
}
}
protected override void OnShown(EventArgs e)
{
BringToFront();
base.OnShown(e);
}
protected override void OnShown(EventArgs e)
{
BringToFront();
base.OnShown(e);
}
}
}
}
}
#endif

View File

@@ -71,7 +71,7 @@ namespace NTwain
/// <returns></returns>
public STS GetCapCurrent<TValue>(CAP cap, out List<TValue> value) where TValue : struct
{
value = new List<TValue>();
value = [];
var sts = GetCapCurrent(cap, out TW_CAPABILITY twcap);
if (sts.RC == TWRC.SUCCESS)
{
@@ -88,7 +88,8 @@ namespace NTwain
}
break;
case TWON.RANGE:
value.Add(twcap.ReadRange<TValue>(this).CurrentValue);
var range = twcap.ReadRange<TValue>(this);
if (range != null) value.Add(range.CurrentValue);
break;
case TWON.ARRAY:
var twarr = twcap.ReadArray<TValue>(this);
@@ -101,6 +102,47 @@ namespace NTwain
return sts;
}
/// <summary>
/// Gets a CAP's current value as boxed values. This is a simplified version that doesn't require
/// manual reading, but may or may not work.
/// </summary>
/// <param name="cap"></param>
/// <param name="value"></param>
/// <returns></returns>
public STS GetCapCurrentBoxed(CAP cap, out List<object> value)
{
value = [];
var sts = GetCapCurrent(cap, out TW_CAPABILITY twcap);
if (sts.RC == TWRC.SUCCESS)
{
switch (twcap.ConType)
{
case TWON.ONEVALUE:
var read = twcap.ReadOneValueBoxed(this);
if (read != null) value.Add(read);
break;
case TWON.ENUMERATION:
var twenum = twcap.ReadEnumerationBoxed(this);
if (twenum.Items != null && twenum.CurrentIndex < twenum.Items.Length)
{
value.Add(twenum.Items[twenum.CurrentIndex]);
}
break;
case TWON.RANGE:
var range = twcap.ReadRangeBoxed(this);
if (range != null) value.Add(range.CurrentValue);
break;
case TWON.ARRAY:
var twarr = twcap.ReadArrayBoxed(this);
if (twarr != null && twarr.Count > 0) value.AddRange(twarr);
break;
default:
twcap.Free(this); break;
}
}
return sts;
}
/// <summary>
/// Gets a CAP's raw default value.
/// Caller will need to manually read and free the memory.
@@ -125,7 +167,7 @@ namespace NTwain
/// <returns></returns>
public STS GetCapDefault<TValue>(CAP cap, out List<TValue> value) where TValue : struct
{
value = new List<TValue>();
value = [];
var sts = GetCapDefault(cap, out TW_CAPABILITY twcap);
if (sts.RC == TWRC.SUCCESS)
{
@@ -142,7 +184,8 @@ namespace NTwain
}
break;
case TWON.RANGE:
value.Add(twcap.ReadRange<TValue>(this).DefaultValue);
var range = twcap.ReadRange<TValue>(this);
if (range != null) value.Add(range.DefaultValue);
break;
case TWON.ARRAY:
var twarr = twcap.ReadArray<TValue>(this);
@@ -155,6 +198,47 @@ namespace NTwain
return sts;
}
/// <summary>
/// Gets a CAP's default value. This is a simplified version that doesn't require
/// manual reading, but may or may not work.
/// </summary>
/// <param name="cap"></param>
/// <param name="value"></param>
/// <returns></returns>
public STS GetCapDefaultBoxed(CAP cap, out List<object> value)
{
value = [];
var sts = GetCapDefault(cap, out TW_CAPABILITY twcap);
if (sts.RC == TWRC.SUCCESS)
{
switch (twcap.ConType)
{
case TWON.ONEVALUE:
var read = twcap.ReadOneValueBoxed(this);
if (read != null) value.Add(read);
break;
case TWON.ENUMERATION:
var twenum = twcap.ReadEnumerationBoxed(this);
if (twenum.Items != null && twenum.DefaultIndex < twenum.Items.Length)
{
value.Add(twenum.Items[twenum.DefaultIndex]);
}
break;
case TWON.RANGE:
var range = twcap.ReadRangeBoxed(this);
if (range != null) value.Add(range.DefaultValue);
break;
case TWON.ARRAY:
var twarr = twcap.ReadArrayBoxed(this);
if (twarr != null && twarr.Count > 0) value.AddRange(twarr);
break;
default:
twcap.Free(this); break;
}
}
return sts;
}
/// <summary>
/// Gets a CAP's raw supported values.
/// Caller will need to manually read and free the memory.
@@ -193,24 +277,12 @@ namespace NTwain
var twenum = twcap.ReadEnumeration<TValue>(this);
if (twenum.Items != null)
{
value.EnumValue = new EnumValue<TValue>
{
CurrentIndex = twenum.CurrentIndex,
DefaultIndex = twenum.DefaultIndex,
Items = twenum.Items
};
value.EnumValue = twenum;
}
break;
case TWON.RANGE:
var range = twcap.ReadRange<TValue>(this);
value.RangeValue = new RangeValue<TValue>
{
Min = range.MinValue,
Max = range.MaxValue,
Step = range.StepSize,
DefaultValue = range.DefaultValue,
CurrentValue = range.CurrentValue
};
value.RangeValue = range;
break;
case TWON.ARRAY:
var twarr = twcap.ReadArray<TValue>(this);
@@ -226,6 +298,51 @@ namespace NTwain
return sts;
}
/// <summary>
/// Gets a CAP's supported values. This is a simplified version that doesn't require
/// manual reading, but may or may not work.
/// </summary>
/// <param name="cap"></param>
/// <param name="value"></param>
/// <returns></returns>
public STS GetCapValuesBoxed(CAP cap, out ValueContainer<object> value)
{
value = new ValueContainer<object> { ContainerType = TWON.DONTCARE };
var sts = GetCapValues(cap, out TW_CAPABILITY twcap);
if (sts.RC == TWRC.SUCCESS)
{
value.ContainerType = twcap.ConType;
switch (twcap.ConType)
{
case TWON.ONEVALUE:
value.OneValue = twcap.ReadOneValueBoxed(this);
break;
case TWON.ENUMERATION:
var twenum = twcap.ReadEnumerationBoxed(this);
if (twenum.Items != null)
{
value.EnumValue = twenum;
}
break;
case TWON.RANGE:
var range = twcap.ReadRangeBoxed(this);
value.RangeValue = range;
break;
case TWON.ARRAY:
var twarr = twcap.ReadArrayBoxed(this);
if (twarr != null)
{
value.ArrayValue = twarr;
}
break;
default:
twcap.Free(this); break;
}
}
return sts;
}
/// <summary>
/// Gets a CAP's help text (description).
/// This may not work due to unclear spec.
@@ -309,8 +426,7 @@ namespace NTwain
}
/// <summary>
/// A simpler cap value setter for common one-value scenarios
/// that's easier to use. Not for other container type sets.
/// A simpler cap value setter for one-value type.
/// </summary>
/// <typeparam name="TValue"></typeparam>
/// <param name="cap"></param>
@@ -322,6 +438,76 @@ namespace NTwain
return SetCap(ref twcap);
}
/// <summary>
/// A cap value setter for enumeration container type.
/// </summary>
/// <typeparam name="TValue"></typeparam>
/// <param name="cap"></param>
/// <param name="value"></param>
/// <returns></returns>
public STS SetCap<TValue>(CAP cap, Enumeration<TValue> value) where TValue : struct
{
var twcap = ValueWriter.CreateEnumCap(cap, this, value);
return SetCap(ref twcap);
}
/// <summary>
/// A cap value setter for range container type.
/// </summary>
/// <typeparam name="TValue"></typeparam>
/// <param name="cap"></param>
/// <param name="value"></param>
/// <returns></returns>
public STS SetCap<TValue>(CAP cap, Range<TValue> value) where TValue : struct
{
var twcap = ValueWriter.CreateRangeCap(cap, this, value);
return SetCap(ref twcap);
}
/// <summary>
/// A cap value setter for array container type.
/// </summary>
/// <typeparam name="TValue"></typeparam>
/// <param name="cap"></param>
/// <param name="value"></param>
/// <returns></returns>
public STS SetCap<TValue>(CAP cap, IList<TValue> value) where TValue : struct
{
var twcap = ValueWriter.CreateArrayCap(cap, this, value);
return SetCap(ref twcap);
}
/// <summary>
/// A cap value setter for all kinds of container types.
/// </summary>
/// <typeparam name="TValue"></typeparam>
/// <param name="cap"></param>
/// <param name="value"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public STS SetCap<TValue>(CAP cap, ValueContainer<TValue> value) where TValue : struct
{
switch (value.ContainerType)
{
case TWON.ONEVALUE:
return SetCap(cap, value.OneValue);
case TWON.ENUMERATION:
if (value.EnumValue == null)
throw new ArgumentException("EnumValue cannot be null when ContainerType is ENUMERATION.", nameof(value));
return SetCap(cap, value.EnumValue);
case TWON.RANGE:
if (value.RangeValue == null)
throw new ArgumentException("RangeValue cannot be null when ContainerType is RANGE.", nameof(value));
return SetCap(cap, value.RangeValue);
case TWON.ARRAY:
if (value.ArrayValue == null)
throw new ArgumentException("ArrayValue cannot be null when ContainerType is ARRAY.", nameof(value));
return SetCap(cap, value.ArrayValue);
default:
throw new ArgumentException("Unsupported ContainerType for setting CAP.", nameof(value));
}
}
/// <summary>
/// Sets a CAP's constraint values.
/// An easy way to create a value is to use the
@@ -338,6 +524,90 @@ namespace NTwain
return WrapInSTS(rc);
}
/// <summary>
/// A simpler cap constraint setter for one-value type.
/// </summary>
/// <typeparam name="TValue"></typeparam>
/// <param name="cap"></param>
/// <param name="value"></param>
/// <returns></returns>
public STS SetConstraint<TValue>(CAP cap, TValue value) where TValue : struct
{
var twcap = ValueWriter.CreateOneValueCap(cap, this, value);
return SetConstraint(ref twcap);
}
/// <summary>
/// A cap constraint setter for enumeration container type.
/// </summary>
/// <typeparam name="TValue"></typeparam>
/// <param name="cap"></param>
/// <param name="value"></param>
/// <returns></returns>
public STS SetConstraint<TValue>(CAP cap, Enumeration<TValue> value) where TValue : struct
{
var twcap = ValueWriter.CreateEnumCap(cap, this, value);
return SetConstraint(ref twcap);
}
/// <summary>
/// A cap constraint setter for range container type.
/// </summary>
/// <typeparam name="TValue"></typeparam>
/// <param name="cap"></param>
/// <param name="value"></param>
/// <returns></returns>
public STS SetConstraint<TValue>(CAP cap, Range<TValue> value) where TValue : struct
{
var twcap = ValueWriter.CreateRangeCap(cap, this, value);
return SetConstraint(ref twcap);
}
/// <summary>
/// A cap constraint setter for array container type.
/// </summary>
/// <typeparam name="TValue"></typeparam>
/// <param name="cap"></param>
/// <param name="value"></param>
/// <returns></returns>
public STS SetConstraint<TValue>(CAP cap, IList<TValue> value) where TValue : struct
{
var twcap = ValueWriter.CreateArrayCap(cap, this, value);
return SetConstraint(ref twcap);
}
/// <summary>
/// A cap constraint setter for all kinds of container types.
/// </summary>
/// <typeparam name="TValue"></typeparam>
/// <param name="cap"></param>
/// <param name="value"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public STS SetConstraint<TValue>(CAP cap, ValueContainer<TValue> value) where TValue : struct
{
switch (value.ContainerType)
{
case TWON.ONEVALUE:
return SetConstraint(cap, value.OneValue);
case TWON.ENUMERATION:
if (value.EnumValue == null)
throw new ArgumentException("EnumValue cannot be null when ContainerType is ENUMERATION.", nameof(value));
return SetConstraint(cap, value.EnumValue);
case TWON.RANGE:
if (value.RangeValue == null)
throw new ArgumentException("RangeValue cannot be null when ContainerType is RANGE.", nameof(value));
return SetConstraint(cap, value.RangeValue);
case TWON.ARRAY:
if (value.ArrayValue == null)
throw new ArgumentException("ArrayValue cannot be null when ContainerType is ARRAY.", nameof(value));
return SetConstraint(cap, value.ArrayValue);
default:
throw new ArgumentException("Unsupported ContainerType for setting CAP constraint.", nameof(value));
}
}
/// <summary>
/// Resets a CAP's current value to power-on default.
/// Caller will need to manually read and free the memory.
@@ -365,30 +635,73 @@ namespace NTwain
/// <param name="cap"></param>
/// <param name="value"></param>
/// <returns></returns>
public STS ResetCap<TValue>(CAP cap, out TValue value) where TValue : struct
public STS ResetCap<TValue>(CAP cap, out List<TValue> value) where TValue : struct
{
value = default;
value = [];
var sts = ResetCap(cap, out TW_CAPABILITY twcap);
if (sts.RC == TWRC.SUCCESS)
{
switch (twcap.ConType)
{
case TWON.ONEVALUE:
value = twcap.ReadOneValue<TValue>(this);
value.Add(twcap.ReadOneValue<TValue>(this));
break;
case TWON.ENUMERATION:
var twenum = twcap.ReadEnumeration<TValue>(this);
if (twenum.Items != null && twenum.CurrentIndex < twenum.Items.Length)
{
value = twenum.Items[twenum.CurrentIndex];
value.Add(twenum.Items[twenum.CurrentIndex]);
}
break;
case TWON.RANGE:
value = twcap.ReadRange<TValue>(this).CurrentValue;
var range = twcap.ReadRange<TValue>(this);
if (range != null) value.Add(range.CurrentValue);
break;
case TWON.ARRAY:
var twarr = twcap.ReadArray<TValue>(this);
if (twarr != null && twarr.Count > 0) value = twarr[0];
if (twarr != null && twarr.Count > 0) value.AddRange(twarr);
break;
default:
twcap.Free(this); break;
}
}
return sts;
}
/// <summary>
/// Resets a CAP's current value to power-on default.
/// </summary>
/// <param name="cap"></param>
/// <param name="value"></param>
/// <returns></returns>
public STS ResetCapBoxed(CAP cap, out List<object> value)
{
value = [];
var sts = ResetCap(cap, out TW_CAPABILITY twcap);
if (sts.RC == TWRC.SUCCESS)
{
switch (twcap.ConType)
{
case TWON.ONEVALUE:
var read = twcap.ReadOneValueBoxed(this);
if (read != null) value.Add(read);
break;
case TWON.ENUMERATION:
var twenum = twcap.ReadEnumerationBoxed(this);
if (twenum.Items != null && twenum.CurrentIndex < twenum.Items.Length)
{
value.Add(twenum.Items[twenum.CurrentIndex]);
}
break;
case TWON.RANGE:
var range = twcap.ReadRangeBoxed(this);
if (range != null) value.Add(range.CurrentValue);
break;
case TWON.ARRAY:
var twarr = twcap.ReadArrayBoxed(this);
if (twarr != null && twarr.Count > 0) value.AddRange(twarr);
break;
default:
twcap.Free(this); break;