diff --git a/src/NTwain/DSM/DSMGenerator.tt b/src/NTwain/DSM/DSMGenerator.tt index 71407ac..b91acd5 100644 --- a/src/NTwain/DSM/DSMGenerator.tt +++ b/src/NTwain/DSM/DSMGenerator.tt @@ -168,6 +168,13 @@ namespace NTwain.DSM DG dg, DAT dat, MSG msg, ref TW_SETUPFILEXFER filexfer ); + [DllImport(DsmName, CharSet = CharSet.Ansi)] + public static extern TWRC DSM_Entry + ( + ref <#= file.identityClass #> origin, ref <#= file.identityClass #> dest, + DG dg, DAT dat, MSG msg, ref TW_METRICS metrics + ); + [DllImport(DsmName, CharSet = CharSet.Ansi)] public static extern TWRC DSM_Entry ( diff --git a/src/NTwain/DSM/Linux64DSM.cs b/src/NTwain/DSM/Linux64DSM.cs index f9f0a53..7713db0 100644 --- a/src/NTwain/DSM/Linux64DSM.cs +++ b/src/NTwain/DSM/Linux64DSM.cs @@ -148,6 +148,13 @@ namespace NTwain.DSM DG dg, DAT dat, MSG msg, ref TW_SETUPFILEXFER filexfer ); + [DllImport(DsmName, CharSet = CharSet.Ansi)] + public static extern TWRC DSM_Entry + ( + ref TW_IDENTITY_LEGACY origin, ref TW_IDENTITY_LEGACY dest, + DG dg, DAT dat, MSG msg, ref TW_METRICS metrics + ); + [DllImport(DsmName, CharSet = CharSet.Ansi)] public static extern TWRC DSM_Entry ( diff --git a/src/NTwain/DSM/LinuxBotched64DSM.cs b/src/NTwain/DSM/LinuxBotched64DSM.cs index cd93707..f94dd42 100644 --- a/src/NTwain/DSM/LinuxBotched64DSM.cs +++ b/src/NTwain/DSM/LinuxBotched64DSM.cs @@ -148,6 +148,13 @@ namespace NTwain.DSM DG dg, DAT dat, MSG msg, ref TW_SETUPFILEXFER filexfer ); + [DllImport(DsmName, CharSet = CharSet.Ansi)] + public static extern TWRC DSM_Entry + ( + ref TW_IDENTITY origin, ref TW_IDENTITY dest, + DG dg, DAT dat, MSG msg, ref TW_METRICS metrics + ); + [DllImport(DsmName, CharSet = CharSet.Ansi)] public static extern TWRC DSM_Entry ( diff --git a/src/NTwain/DSM/LinuxDSM.cs b/src/NTwain/DSM/LinuxDSM.cs index 4d1ae05..bddb350 100644 --- a/src/NTwain/DSM/LinuxDSM.cs +++ b/src/NTwain/DSM/LinuxDSM.cs @@ -148,6 +148,13 @@ namespace NTwain.DSM DG dg, DAT dat, MSG msg, ref TW_SETUPFILEXFER filexfer ); + [DllImport(DsmName, CharSet = CharSet.Ansi)] + public static extern TWRC DSM_Entry + ( + ref TW_IDENTITY_LEGACY origin, ref TW_IDENTITY_LEGACY dest, + DG dg, DAT dat, MSG msg, ref TW_METRICS metrics + ); + [DllImport(DsmName, CharSet = CharSet.Ansi)] public static extern TWRC DSM_Entry ( diff --git a/src/NTwain/DSM/OSXLegacyDSM.cs b/src/NTwain/DSM/OSXLegacyDSM.cs index 2b17618..3fb1293 100644 --- a/src/NTwain/DSM/OSXLegacyDSM.cs +++ b/src/NTwain/DSM/OSXLegacyDSM.cs @@ -148,6 +148,13 @@ namespace NTwain.DSM DG dg, DAT dat, MSG msg, ref TW_SETUPFILEXFER filexfer ); + [DllImport(DsmName, CharSet = CharSet.Ansi)] + public static extern TWRC DSM_Entry + ( + ref TW_IDENTITY_MACOSX origin, ref TW_IDENTITY_MACOSX dest, + DG dg, DAT dat, MSG msg, ref TW_METRICS metrics + ); + [DllImport(DsmName, CharSet = CharSet.Ansi)] public static extern TWRC DSM_Entry ( diff --git a/src/NTwain/DSM/OSXNewDSM.cs b/src/NTwain/DSM/OSXNewDSM.cs index d0fce04..0613a09 100644 --- a/src/NTwain/DSM/OSXNewDSM.cs +++ b/src/NTwain/DSM/OSXNewDSM.cs @@ -148,6 +148,13 @@ namespace NTwain.DSM DG dg, DAT dat, MSG msg, ref TW_SETUPFILEXFER filexfer ); + [DllImport(DsmName, CharSet = CharSet.Ansi)] + public static extern TWRC DSM_Entry + ( + ref TW_IDENTITY_MACOSX origin, ref TW_IDENTITY_MACOSX dest, + DG dg, DAT dat, MSG msg, ref TW_METRICS metrics + ); + [DllImport(DsmName, CharSet = CharSet.Ansi)] public static extern TWRC DSM_Entry ( diff --git a/src/NTwain/DSM/WinLegacyDSM.cs b/src/NTwain/DSM/WinLegacyDSM.cs index 0e556ca..4398b08 100644 --- a/src/NTwain/DSM/WinLegacyDSM.cs +++ b/src/NTwain/DSM/WinLegacyDSM.cs @@ -148,6 +148,13 @@ namespace NTwain.DSM DG dg, DAT dat, MSG msg, ref TW_SETUPFILEXFER filexfer ); + [DllImport(DsmName, CharSet = CharSet.Ansi)] + public static extern TWRC DSM_Entry + ( + ref TW_IDENTITY_LEGACY origin, ref TW_IDENTITY_LEGACY dest, + DG dg, DAT dat, MSG msg, ref TW_METRICS metrics + ); + [DllImport(DsmName, CharSet = CharSet.Ansi)] public static extern TWRC DSM_Entry ( diff --git a/src/NTwain/DSM/WinNewDSM.cs b/src/NTwain/DSM/WinNewDSM.cs index e46fc83..e6b136e 100644 --- a/src/NTwain/DSM/WinNewDSM.cs +++ b/src/NTwain/DSM/WinNewDSM.cs @@ -148,6 +148,13 @@ namespace NTwain.DSM DG dg, DAT dat, MSG msg, ref TW_SETUPFILEXFER filexfer ); + [DllImport(DsmName, CharSet = CharSet.Ansi)] + public static extern TWRC DSM_Entry + ( + ref TW_IDENTITY_LEGACY origin, ref TW_IDENTITY_LEGACY dest, + DG dg, DAT dat, MSG msg, ref TW_METRICS metrics + ); + [DllImport(DsmName, CharSet = CharSet.Ansi)] public static extern TWRC DSM_Entry ( diff --git a/src/NTwain/Data/TWAINH_EXTRAS.cs b/src/NTwain/Data/TWAINH_EXTRAS.cs index 5770886..d4a03ba 100644 --- a/src/NTwain/Data/TWAINH_EXTRAS.cs +++ b/src/NTwain/Data/TWAINH_EXTRAS.cs @@ -86,7 +86,7 @@ namespace NTwain.Data /// Don't care values... /// public const byte TWON_DONTCARE8 = 0xff; - public const ushort TWON_DONTCARE16 = 0xff; + public const ushort TWON_DONTCARE16 = 0xffff; public const uint TWON_DONTCARE32 = 0xffffffff; /// /// We're departing from a strict translation of H so that @@ -144,10 +144,15 @@ namespace NTwain.Data /// public TWRC ReturnCode; + /// + /// Status if code is failure. + /// + public TW_STATUS Status; + /// /// The response of the task in JSON if successful. /// - public string ResponseJson; + public string? ResponseJson; } public enum TWRC : ushort @@ -173,6 +178,7 @@ namespace NTwain.Data { // Condition codes (always associated with TWRC_FAILURE)... CUSTOMBASE = 0x8000, + None = 0, BUMMER = 1, LOWMEMORY = 2, NODS = 3, diff --git a/src/NTwain/Data/ValueReader.cs b/src/NTwain/Data/ValueReader.cs index 14a87b3..dd8e3e5 100644 --- a/src/NTwain/Data/ValueReader.cs +++ b/src/NTwain/Data/ValueReader.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -17,24 +18,22 @@ namespace NTwain.Data /// Pointer to string. /// Number of bytes to read. /// - public static string? PtrToStringUTF8(IMemoryManager memMgr, IntPtr data, int length) + public static unsafe string? PtrToStringUTF8(IMemoryManager memMgr, IntPtr data, int length) { string? val = null; var locked = memMgr.Lock(data); if (locked != IntPtr.Zero) { - // does this work? who knows. 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); + //var bytes = new byte[length]; + //Marshal.Copy(locked, bytes, 0, bytes.Length); + //val = Encoding.UTF8.GetString(bytes); - //// unsafe method with 1 copy (does it work?) - //sbyte* bytes = (sbyte*)locked; - //val = new string(bytes, 0, length, Encoding.UTF8); + // does this work? + val = Encoding.UTF8.GetString((byte*)locked, length); #else val = Marshal.PtrToStringUTF8(locked, length); #endif diff --git a/src/NTwain/Data/ValueWriter.cs b/src/NTwain/Data/ValueWriter.cs index 92c5125..d9d1773 100644 --- a/src/NTwain/Data/ValueWriter.cs +++ b/src/NTwain/Data/ValueWriter.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; @@ -9,46 +10,29 @@ namespace NTwain.Data /// static class ValueWriter { - ///// - ///// Allocates and copies the string value into a pointer in UTF8 that's null-terminated. - ///// - ///// - ///// - ///// Actual number of bytes used to encode the string without the null. - ///// - //public static unsafe IntPtr StringToPtrUTF8(IMemoryManager memMgr, string value, out int length) - //{ - // if (value == null) - // { - // length = 0; - // return IntPtr.Zero; - // } + /// + /// Allocates and copies the string value into a pointer in UTF8 that's null-terminated. + /// + /// + /// + /// Final length to use with the pointer (includes the null). + /// + public static unsafe IntPtr StringToPtrUTF8(IMemoryManager memMgr, string? value, out uint finalLength) + { + finalLength = 0; + if (value == null) return IntPtr.Zero; - // var utf8 = Encoding.UTF8; - // length = utf8.GetByteCount(value); - - // var ptr = memMgr.Alloc((uint)length + 1); // +1 for null-terminated - - // // TODO: test if this works - // int written; - // byte* bytes = (byte*)ptr; - // try - // { - // // fixed for managed pointer - // fixed (char* firstChar = value) - // { - // written = Encoding.UTF8.GetBytes(firstChar, value.Length, bytes, length); - // } - - // bytes[written] = 0; - // } - // finally - // { - // if (ptr != IntPtr.Zero) memMgr.Free(ptr); - // } - - // return ptr; - //} + fixed (char* pInput = value) + { + var len = Encoding.UTF8.GetByteCount(pInput, value.Length); + finalLength = (uint)len + 1; + var pResult = (byte*)memMgr.Alloc(finalLength); + var bytesWritten = Encoding.UTF8.GetBytes(pInput, value.Length, pResult, len); + Trace.Assert(len == bytesWritten); + pResult[len] = 0; + return (IntPtr)pResult; + } + } diff --git a/src/NTwain/NTwain.csproj b/src/NTwain/NTwain.csproj index 0f27b17..bd52a54 100644 --- a/src/NTwain/NTwain.csproj +++ b/src/NTwain/NTwain.csproj @@ -5,7 +5,7 @@ Library containing the TWAIN API for dotnet. net6.0;net6.0-windows;net462; enable - + true diff --git a/src/NTwain/Triplets/ControlDATs/Metrics.cs b/src/NTwain/Triplets/ControlDATs/Metrics.cs new file mode 100644 index 0000000..6f9f4e1 --- /dev/null +++ b/src/NTwain/Triplets/ControlDATs/Metrics.cs @@ -0,0 +1,45 @@ +using NTwain.Data; +using NTwain.DSM; +using System.Runtime.InteropServices; + +namespace NTwain.Triplets.ControlDATs +{ + /// + /// Contains calls used with and . + /// + public class Metrics + { + public TWRC Get(ref TW_IDENTITY_LEGACY app, ref TW_IDENTITY_LEGACY ds, out TW_METRICS data) + { + data = default; + data.SizeOf = (uint)Marshal.SizeOf(); + + var rc = TWRC.FAILURE; + if (TwainPlatform.IsWindows) + { + if (TwainPlatform.Is32bit && TwainPlatform.PreferLegacyDSM) + { + rc = WinLegacyDSM.DSM_Entry(ref app, ref ds, DG.CONTROL, DAT.METRICS, MSG.GET, ref data); + } + else + { + rc = WinNewDSM.DSM_Entry(ref app, ref ds, DG.CONTROL, DAT.METRICS, MSG.GET, ref data); + } + } + else if (TwainPlatform.IsMacOSX) + { + TW_IDENTITY_MACOSX app2 = app; + TW_IDENTITY_MACOSX ds2 = ds; + if (TwainPlatform.PreferLegacyDSM) + { + rc = OSXLegacyDSM.DSM_Entry(ref app2, ref ds2, DG.CONTROL, DAT.METRICS, MSG.GET, ref data); + } + else + { + rc = OSXNewDSM.DSM_Entry(ref app2, ref ds2, DG.CONTROL, DAT.METRICS, MSG.GET, ref data); + } + } + return rc; + } + } +} diff --git a/src/NTwain/Triplets/DGControl.cs b/src/NTwain/Triplets/DGControl.cs index 55ca624..f610c3c 100644 --- a/src/NTwain/Triplets/DGControl.cs +++ b/src/NTwain/Triplets/DGControl.cs @@ -27,6 +27,8 @@ namespace NTwain.Triplets public static readonly Identity Identity = new(); + public static readonly Metrics Metrics = new(); + public static readonly Parent Parent = new(); public static readonly Passthru Passthru = new(); diff --git a/src/NTwain/TwainAppSession.PropEvents.cs b/src/NTwain/TwainAppSession.PropEvents.cs index 3a07809..84dd050 100644 --- a/src/NTwain/TwainAppSession.PropEvents.cs +++ b/src/NTwain/TwainAppSession.PropEvents.cs @@ -72,8 +72,8 @@ namespace NTwain { get { - var sts = DGControl.CustomDsData.Get(ref _appIdentity, ref _currentDS, out TW_CUSTOMDSDATA data); - if (sts == TWRC.SUCCESS) + var rc = DGControl.CustomDsData.Get(ref _appIdentity, ref _currentDS, out TW_CUSTOMDSDATA data); + if (rc == TWRC.SUCCESS) { if (data.hData != IntPtr.Zero && data.InfoLength > 0) { @@ -105,7 +105,7 @@ namespace NTwain var lockedPtr = Lock(data.hData); Marshal.Copy(value, 0, lockedPtr, value.Length); Unlock(data.hData); - var sts = DGControl.CustomDsData.Set(ref _appIdentity, ref _currentDS, ref data); + var rc = DGControl.CustomDsData.Set(ref _appIdentity, ref _currentDS, ref data); } finally { diff --git a/src/NTwain/TwainAppSession.Sources.cs b/src/NTwain/TwainAppSession.Sources.cs index 476d5d7..d727cce 100644 --- a/src/NTwain/TwainAppSession.Sources.cs +++ b/src/NTwain/TwainAppSession.Sources.cs @@ -1,6 +1,8 @@ using NTwain.Data; using NTwain.Triplets; +using System; using System.Collections.Generic; +using System.Runtime.InteropServices; namespace NTwain { @@ -132,5 +134,49 @@ namespace NTwain } return WrapInSTS(rc); } + + /// + /// Reads information relating to the last capture run. + /// Only valid on state 4 after a capture. + /// + public STS GetMetrics(out TW_METRICS metrics) + { + return WrapInSTS(DGControl.Metrics.Get(ref _appIdentity, ref _currentDS, out metrics)); + } + + /// + /// 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 = TWRC.FAILURE }; + TW_TWAINDIRECT task = default; + try + { + task.SizeOf = (uint)Marshal.SizeOf(typeof(TW_TWAINDIRECT)); + task.CommunicationManager = communicationManager; + task.Send = ValueWriter.StringToPtrUTF8(this, taskJson, out uint length); + task.SendSize = length; + + result.ReturnCode = DGControl.TwainDirect.SetTask(ref _appIdentity, ref _currentDS, ref task); + if (result.ReturnCode == TWRC.FAILURE) + { + result.Status = GetLastStatus(); + } + else if (result.ReturnCode == TWRC.SUCCESS && task.ReceiveSize > 0 && task.Receive != IntPtr.Zero) + { + result.ResponseJson = ValueReader.PtrToStringUTF8(this, task.Receive, (int)task.ReceiveSize); + } + } + finally + { + //if (task.Send != IntPtr.Zero) Free(task.Send); // does source free the Send? + if (task.Receive != IntPtr.Zero) Free(task.Receive); + } + return result; + } } }