Attempt to make boxed version of cap value reads.

This commit is contained in:
Eugene Wang
2025-11-20 22:56:23 -05:00
parent de788ce91d
commit ce96d0c361
4 changed files with 208 additions and 9 deletions

View File

@@ -1,10 +1,11 @@
using NTwain.Data; using NTwain.Data;
using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace NTwain.Caps; namespace NTwain.Caps;
public record ValueContainer<TValue> where TValue : struct public record ValueContainer<TValue>
{ {
public TWON ContainerType { get; set; } public TWON ContainerType { get; set; }
@@ -20,7 +21,7 @@ public record ValueContainer<TValue> where TValue : struct
{ {
return ContainerType switch return ContainerType switch
{ {
TWON.ONEVALUE => OneValue.HasValue ? ToEnumerable(OneValue.Value) : [], TWON.ONEVALUE => ToEnumerable(OneValue),
TWON.ARRAY => ArrayValue ?? [], TWON.ARRAY => ArrayValue ?? [],
TWON.ENUMERATION => EnumValue?.Items ?? [], TWON.ENUMERATION => EnumValue?.Items ?? [],
TWON.RANGE => RangeValue != null ? GenerateRangeValues(RangeValue) : [], TWON.RANGE => RangeValue != null ? GenerateRangeValues(RangeValue) : [],
@@ -28,22 +29,27 @@ public record ValueContainer<TValue> where TValue : struct
}; };
} }
private IEnumerable<TValue> ToEnumerable(TValue value) private IEnumerable<TValue> ToEnumerable(TValue? value)
{ {
if (value == null) yield break;
yield return value; yield return value;
} }
private IEnumerable<TValue> GenerateRangeValues(RangeValue<TValue> range) private IEnumerable<TValue> GenerateRangeValues(RangeValue<TValue> range)
{ {
var de = new DynamicEnumerator<TValue>(range.Min, range.Max, range.Step); var dynamicType = typeof(DynamicEnumerator<>);
var genericType = dynamicType.MakeGenericType(typeof(TValue));
var de = Activator.CreateInstance(genericType, range.Min, range.Max, range.Step) as IEnumerator;
if (de == null) yield break;
while (de.MoveNext()) while (de.MoveNext())
{ {
yield return de.Current; yield return (TValue)de.Current;
} }
} }
} }
public record EnumValue<TValue> where TValue : struct public record EnumValue<TValue>
{ {
public TValue[] Items { get; set; } = []; public TValue[] Items { get; set; } = [];
@@ -52,7 +58,7 @@ public record EnumValue<TValue> where TValue : struct
public int DefaultIndex { get; set; } public int DefaultIndex { get; set; }
} }
public record RangeValue<TValue> where TValue : struct public record RangeValue<TValue>
{ {
public TValue Min { get; set; } public TValue Min { get; set; }

View File

@@ -292,6 +292,17 @@ namespace NTwain.Data
return ((IEnumerable<TValue>)this).GetEnumerator(); return ((IEnumerable<TValue>)this).GetEnumerator();
} }
} }
/// <summary>
/// A more dotnet-friendly representation of <see cref="TW_RANGE"/> with boxed values.
/// </summary>
public partial class RangeBoxed
{
public object MinValue;
public object MaxValue;
public object StepSize;
public object DefaultValue;
public object CurrentValue;
}
partial struct TW_FIX32 : IEquatable<TW_FIX32>, IConvertible partial struct TW_FIX32 : IEquatable<TW_FIX32>, IConvertible
{ {

View File

@@ -87,7 +87,6 @@ namespace NTwain.Data
/// <summary> /// <summary>
/// Reads a boxed one value out of a cap. This can only be done once if memory is freed. /// Reads a boxed one value out of a cap. This can only be done once if memory is freed.
/// </summary> /// </summary>
/// <typeparam name="TValue"></typeparam>
/// <param name="cap"></param> /// <param name="cap"></param>
/// <param name="memMgr"></param> /// <param name="memMgr"></param>
/// <param name="freeMemory"></param> /// <param name="freeMemory"></param>
@@ -414,6 +413,52 @@ namespace NTwain.Data
} }
} }
public static RangeBoxed ReadRangeBoxed(this ref TW_CAPABILITY cap, IMemoryManager memMgr, bool freeMemory = true)
{
var retVal = new RangeBoxed();
if (cap.ConType != TWON.RANGE || cap.hContainer == IntPtr.Zero) return retVal;
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;
}
retVal.MinValue = ReadTWTYDataBoxed(lockedPtr, itemType, 0);
lockedPtr += 4;
retVal.MaxValue = ReadTWTYDataBoxed(lockedPtr, itemType, 0);
lockedPtr += 4;
retVal.StepSize = ReadTWTYDataBoxed(lockedPtr, itemType, 0);
lockedPtr += 4;
retVal.CurrentValue = ReadTWTYDataBoxed(lockedPtr, itemType, 0);
lockedPtr += 4;
retVal.DefaultValue = ReadTWTYDataBoxed(lockedPtr, itemType, 0);
lockedPtr += 4;
return retVal;
}
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 public static Range<TValue> ReadRange<TValue>(this ref TW_CAPABILITY cap, IMemoryManager memMgr, bool freeMemory = true) where TValue : struct
{ {
var retVal = new Range<TValue>(); var retVal = new Range<TValue>();

View File

@@ -101,6 +101,46 @@ namespace NTwain
return sts; 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 = new List<object>();
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:
value.Add(twcap.ReadRangeBoxed(this).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> /// <summary>
/// Gets a CAP's raw default value. /// Gets a CAP's raw default value.
/// Caller will need to manually read and free the memory. /// Caller will need to manually read and free the memory.
@@ -155,6 +195,46 @@ namespace NTwain
return sts; 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 = new List<object>();
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:
value.Add(twcap.ReadRangeBoxed(this).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> /// <summary>
/// Gets a CAP's raw supported values. /// Gets a CAP's raw supported values.
/// Caller will need to manually read and free the memory. /// Caller will need to manually read and free the memory.
@@ -226,6 +306,63 @@ namespace NTwain
return sts; 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 = new EnumValue<object>
{
CurrentIndex = twenum.CurrentIndex,
DefaultIndex = twenum.DefaultIndex,
Items = twenum.Items
};
}
break;
case TWON.RANGE:
var range = twcap.ReadRangeBoxed(this);
value.RangeValue = new RangeValue<object>
{
Min = range.MinValue,
Max = range.MaxValue,
Step = range.StepSize,
DefaultValue = range.DefaultValue,
CurrentValue = range.CurrentValue
};
break;
case TWON.ARRAY:
var twarr = twcap.ReadArrayBoxed(this);
if (twarr != null)
{
value.ArrayValue = twarr;
}
break;
default:
twcap.Free(this); break;
}
}
return sts;
}
/// <summary> /// <summary>
/// Gets a CAP's help text (description). /// Gets a CAP's help text (description).
/// This may not work due to unclear spec. /// This may not work due to unclear spec.