using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Text; namespace NTwain.Data { /// /// Contains methods for reading pointers into various things. /// public static class ValueReader { /// /// Reads pointer as UTF8 string. /// /// Pointer to string. /// /// Number of bytes to read. /// public static unsafe string? PtrToStringUTF8(this IntPtr data, IMemoryManager memMgr, int length) { string? val = null; var locked = memMgr.Lock(data); if (locked != IntPtr.Zero) { try { #if NETFRAMEWORK // safe method but with 2 copies (arr and parsed string) //var bytes = new byte[length]; //Marshal.Copy(locked, bytes, 0, bytes.Length); //val = Encoding.UTF8.GetString(bytes); // does this work? val = Encoding.UTF8.GetString((byte*)locked, length); #else val = Marshal.PtrToStringUTF8(locked, length); #endif } finally { memMgr.Unlock(data); } } return val; } static T MarshalTo(IntPtr ptr) => Marshal.PtrToStructure(ptr)!; // these contain parts from the original TWAIN.CapabilityToCsv() public static TWTY DetermineValueType(this ref TW_CAPABILITY cap, IMemoryManager memMgr, bool freeMemory = true) { var type = TWTY.Invalid; var lockedPtr = memMgr.Lock(cap.hContainer); try { if (TwainPlatform.IsMacOSX) { type = (TWTY)(ushort)(uint)Marshal.ReadInt32(lockedPtr); } else { type = (TWTY)(ushort)Marshal.ReadInt16(lockedPtr); } } finally { if (lockedPtr != IntPtr.Zero) memMgr.Unlock(cap.hContainer); if (freeMemory) { memMgr.Free(cap.hContainer); cap.hContainer = IntPtr.Zero; } } return type; } /// /// Reads a one value out of a cap. This can only be done once if memory is freed. /// /// /// /// /// /// public static TValue ReadOneValue(this ref TW_CAPABILITY cap, IMemoryManager memMgr, bool freeMemory = true) where TValue : struct { 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 (TwainPlatform.IsMacOSX) { // Crack the container... var onevalue = MarshalTo(lockedPtr); itemType = (TWTY)onevalue.ItemType; lockedPtr += Marshal.SizeOf(onevalue); } else { // Crack the container... var onevalue = MarshalTo(lockedPtr); itemType = onevalue.ItemType; lockedPtr += Marshal.SizeOf(onevalue); } return ReadContainerData(lockedPtr, itemType, 0); } finally { if (lockedPtr != IntPtr.Zero) memMgr.Unlock(cap.hContainer); if (freeMemory) { memMgr.Free(cap.hContainer); cap.hContainer = IntPtr.Zero; } } } public static Enumeration ReadEnumeration(this ref TW_CAPABILITY cap, IMemoryManager memMgr, bool freeMemory = true) where TValue : struct { Enumeration 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 (TwainPlatform.IsMacOSX) { // Crack the container... var twenumerationmacosx = MarshalTo(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(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(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 TValue[count]; for (var i = 0; i < count; i++) { retVal.Items[i] = ReadContainerData(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 IList ReadArray(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(); var lockedPtr = memMgr.Lock(cap.hContainer); try { TWTY itemType; uint count; // Mac has a level of indirection and a different structure (ick)... if (TwainPlatform.IsMacOSX) { // Crack the container... var twarraymacosx = MarshalTo(lockedPtr); itemType = (TWTY)twarraymacosx.ItemType; count = twarraymacosx.NumItems; lockedPtr += Marshal.SizeOf(twarraymacosx); } else { // Crack the container... var twarray = MarshalTo(lockedPtr); itemType = twarray.ItemType; count = twarray.NumItems; lockedPtr += Marshal.SizeOf(twarray); } var arr = new TValue[count]; for (var i = 0; i < count; i++) { arr[i] = ReadContainerData(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 Range ReadRange(this ref TW_CAPABILITY cap, IMemoryManager memMgr, bool freeMemory = true) where TValue : struct { var retVal = new Range(); if (cap.ConType != TWON.RANGE || cap.hContainer == IntPtr.Zero) return retVal; var lockedPtr = memMgr.Lock(cap.hContainer); try { TW_RANGE twrange = default; TW_RANGE_FIX32 twrangefix32 = default; // Mac has a level of indirection and a different structure (ick)... if (TwainPlatform.IsMacOSX) { var twrangemacosx = MarshalTo(lockedPtr); var twrangefix32macosx = MarshalTo(lockedPtr); twrange.ItemType = (TWTY)twrangemacosx.ItemType; twrange.MinValue = twrangemacosx.MinValue; twrange.MaxValue = twrangemacosx.MaxValue; twrange.StepSize = twrangemacosx.StepSize; twrange.DefaultValue = twrangemacosx.DefaultValue; twrange.CurrentValue = twrangemacosx.CurrentValue; twrangefix32.ItemType = (TWTY)twrangefix32macosx.ItemType; twrangefix32.MinValue = twrangefix32macosx.MinValue; twrangefix32.MaxValue = twrangefix32macosx.MaxValue; twrangefix32.StepSize = twrangefix32macosx.StepSize; twrangefix32.DefaultValue = twrangefix32macosx.DefaultValue; twrangefix32.CurrentValue = twrangefix32macosx.CurrentValue; } // Windows or the 2.4+ Linux DSM... else { twrange = MarshalTo(lockedPtr); twrangefix32 = MarshalTo(lockedPtr); } // The -2.3 Linux DSM... //else //{ // var twrangelinux64 = MarshalTo(lockedPtr); // var twrangefix32macosx = MarshalTo(lockedPtr); // twrange.ItemType = twrangelinux64.ItemType; // twrange.MinValue = (uint)twrangelinux64.MinValue; // twrange.MaxValue = (uint)twrangelinux64.MaxValue; // twrange.StepSize = (uint)twrangelinux64.StepSize; // twrange.DefaultValue = (uint)twrangelinux64.DefaultValue; // twrange.CurrentValue = (uint)twrangelinux64.CurrentValue; // twrangefix32.ItemType = (TWTY)twrangefix32macosx.ItemType; // twrangefix32.MinValue = twrangefix32macosx.MinValue; // twrangefix32.MaxValue = twrangefix32macosx.MaxValue; // twrangefix32.StepSize = twrangefix32macosx.StepSize; // twrangefix32.DefaultValue = twrangefix32macosx.DefaultValue; // twrangefix32.CurrentValue = twrangefix32macosx.CurrentValue; //} switch (twrange.ItemType) { // use dynamic since I know they fit the type. case TWTY.FIX32: retVal.MinValue = (dynamic)twrangefix32.MinValue; retVal.MaxValue = (dynamic)twrangefix32.MaxValue; retVal.StepSize = (dynamic)twrangefix32.StepSize; retVal.CurrentValue = (dynamic)twrangefix32.CurrentValue; retVal.DefaultValue = (dynamic)twrangefix32.DefaultValue; break; case TWTY.INT8: case TWTY.UINT8: case TWTY.INT16: case TWTY.BOOL: case TWTY.UINT16: case TWTY.INT32: case TWTY.UINT32: retVal.MinValue = (dynamic)twrange.MinValue; retVal.MaxValue = (dynamic)twrange.MaxValue; retVal.StepSize = (dynamic)twrange.StepSize; retVal.CurrentValue = (dynamic)twrange.CurrentValue; retVal.DefaultValue = (dynamic)twrange.DefaultValue; break; default: throw new NotSupportedException($"The value type {twrange.ItemType} is not supported as range."); } return retVal; } finally { if (lockedPtr != IntPtr.Zero) memMgr.Unlock(cap.hContainer); if (freeMemory) { memMgr.Free(cap.hContainer); cap.hContainer = IntPtr.Zero; } } } /// /// Read the pointer as string. This is for cap's one value string pointer /// that /// /// /// /// /// public static string? ReadString(this ref TW_CAPABILITY cap, IMemoryManager memMgr, bool freeMemory = true) { if (cap.hContainer == IntPtr.Zero) return null; var lockedPtr = memMgr.Lock(cap.hContainer); try { if (cap.ConType == TWON.ONEVALUE) { TWTY itemType; // Mac has a level of indirection and a different structure (ick)... if (TwainPlatform.IsMacOSX) { // Crack the container... var onevalue = MarshalTo(lockedPtr); itemType = (TWTY)onevalue.ItemType; lockedPtr += Marshal.SizeOf(onevalue); } else { // Crack the container... var onevalue = MarshalTo(lockedPtr); itemType = onevalue.ItemType; lockedPtr += Marshal.SizeOf(onevalue); } switch (itemType) { case TWTY.STR32: return MarshalTo(lockedPtr).ToString(); case TWTY.STR64: return MarshalTo(lockedPtr).ToString(); case TWTY.STR128: return MarshalTo(lockedPtr).ToString(); case TWTY.STR255: return MarshalTo(lockedPtr).ToString(); case TWTY.HANDLE: // TODO: how to determine the length of this thing??? // null-terminated and encoded string? // good chance this ain't right. using (var stream = new MemoryStream()) { byte read = Marshal.ReadByte(lockedPtr); while (read != 0) { stream.WriteByte(read); read = Marshal.ReadByte(lockedPtr); lockedPtr += 1; } // which one? //return Encoding.Unicode.GetString(Encoding.Convert(Language.GetEncoding(), Encoding.Unicode, stream.ToArray())); return Language.GetEncoding().GetString(stream.ToArray()); } } } } finally { if (lockedPtr != IntPtr.Zero) memMgr.Unlock(cap.hContainer); if (freeMemory) { memMgr.Free(cap.hContainer); cap.hContainer = IntPtr.Zero; } } return null; } /// /// Read the container pointer content. /// /// A locked pointer to the container's data pointer. If data is array this is the 0th item. /// The twain type. /// Index of the item if pointer is array. /// static TValue ReadContainerData(IntPtr intptr, TWTY type, int itemIndex) where TValue : struct { var isEnum = typeof(TValue).IsEnum; switch (type) { default: throw new NotSupportedException($"Unsupported item type {type} for reading."); // TODO: verify if needs to read int32 for small types case TWTY.INT8: intptr += 1 * itemIndex; if (isEnum) { return NumericToEnum(MarshalTo(intptr)); } return MarshalTo(intptr); case TWTY.UINT8: intptr += 1 * itemIndex; if (isEnum) { return NumericToEnum(MarshalTo(intptr)); } return MarshalTo(intptr); case TWTY.INT16: intptr += 2 * itemIndex; if (isEnum) { return NumericToEnum(MarshalTo(intptr)); } return MarshalTo(intptr); case TWTY.BOOL: case TWTY.UINT16: intptr += 2 * itemIndex; if (isEnum) { return NumericToEnum(MarshalTo(intptr)); } return MarshalTo(intptr); case TWTY.INT32: intptr += 4 * itemIndex; if (isEnum) { return NumericToEnum(MarshalTo(intptr)); } return MarshalTo(intptr); case TWTY.UINT32: intptr += 4 * itemIndex; if (isEnum) { return NumericToEnum(MarshalTo(intptr)); } return MarshalTo(intptr); case TWTY.FIX32: intptr += 4 * itemIndex; return MarshalTo(intptr); case TWTY.FRAME: intptr += 16 * itemIndex; return MarshalTo(intptr); case TWTY.STR32: intptr += TW_STR32.Size * itemIndex; return MarshalTo(intptr); case TWTY.STR64: intptr += TW_STR64.Size * itemIndex; return MarshalTo(intptr); case TWTY.STR128: intptr += TW_STR128.Size * itemIndex; return MarshalTo(intptr); case TWTY.STR255: intptr += TW_STR255.Size * itemIndex; return MarshalTo(intptr); } } static TEnum NumericToEnum(TNumber num) where TEnum : struct { // TODO: some caps returns a data type that's not the underlying datatype for the enum // so foolproof way is to ToString() it and parse it as the enum type. // this is bad for perf so find better way later var str = num!.ToString(); if (Enum.TryParse(str, out TEnum parsed)) { return parsed; } return default; } } }