diff --git a/NTwain.Net35/NTwain.Net35.csproj b/NTwain.Net35/NTwain.Net35.csproj index c1e435b..259a426 100644 --- a/NTwain.Net35/NTwain.Net35.csproj +++ b/NTwain.Net35/NTwain.Net35.csproj @@ -79,6 +79,9 @@ Internals\ITwainSessionInternal.cs + + Internals\MESSAGE.cs + Internals\MessageLoop.cs @@ -103,6 +106,9 @@ ITwainSession.cs + + IWinMessageFilter.cs + Platform.cs diff --git a/NTwain/IWinMessageFilter.cs b/NTwain/IWinMessageFilter.cs new file mode 100644 index 0000000..abfd76a --- /dev/null +++ b/NTwain/IWinMessageFilter.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NTwain +{ + /// + /// Interface for checking whether messages from WndProc is a TWAIN message and is handled + /// internally. + /// + interface IWinMessageFilter + { + /// + /// Checks and handle the message if it's a TWAIN message. + /// + /// The window handle. + /// The message. + /// The w parameter. + /// The l parameter. + /// true if handled internally. + bool IsTwainMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam); + } + + +} diff --git a/NTwain/Internals/ITwainSessionInternal.cs b/NTwain/Internals/ITwainSessionInternal.cs index f9c9603..54ed089 100644 --- a/NTwain/Internals/ITwainSessionInternal.cs +++ b/NTwain/Internals/ITwainSessionInternal.cs @@ -14,6 +14,8 @@ namespace NTwain.Internals /// TWIdentity AppId { get; } + MessageLoop SelfMessageLoop { get; } + /// /// Gets or sets a value indicating whether calls to triplets will verify the current twain session state. /// diff --git a/NTwain/Internals/MESSAGE.cs b/NTwain/Internals/MESSAGE.cs new file mode 100644 index 0000000..7a04c7c --- /dev/null +++ b/NTwain/Internals/MESSAGE.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace NTwain.Internals +{ + + + /// + /// The MSG structure in Windows for TWAIN use. + /// + [StructLayout(LayoutKind.Sequential)] + public struct MESSAGE + { + public MESSAGE(IntPtr hwnd, int message, IntPtr wParam, IntPtr lParam) + { + _hwnd = hwnd; + _message = (uint)message; + _wParam = wParam; + _lParam = lParam; + _time = 0; + _x = 0; + _y = 0; + } + + IntPtr _hwnd; + uint _message; + IntPtr _wParam; + IntPtr _lParam; + uint _time; + int _x; + int _y; + } +} diff --git a/NTwain/Internals/MessageLoop.cs b/NTwain/Internals/MessageLoop.cs index 82aaa54..885919a 100644 --- a/NTwain/Internals/MessageLoop.cs +++ b/NTwain/Internals/MessageLoop.cs @@ -12,18 +12,21 @@ namespace NTwain.Internals /// class MessageLoop { - static MessageLoop _instance = new MessageLoop(); - public static MessageLoop Instance { get { return _instance; } } - Dispatcher _dispatcher; WindowsHook _hook; - private MessageLoop() { } - public void EnsureStarted(WindowsHook.WndProcHook hook) + public void Stop() + { + if (_dispatcher != null) + { + _dispatcher.InvokeShutdown(); + } + } + public void Start(IWinMessageFilter filter) { if (_dispatcher == null) { - // using this terrible hack so the new thread will start running before this function returns + // using this hack so the new thread will start running before this function returns using (var hack = new WrappedManualResetEvent()) { var loopThread = new Thread(new ThreadStart(() => @@ -32,11 +35,11 @@ namespace NTwain.Internals _dispatcher = Dispatcher.CurrentDispatcher; if (!Platform.IsOnMono) { - _hook = new WindowsHook(hook); + _hook = new WindowsHook(filter); } hack.Set(); Dispatcher.Run(); - // if for whatever reason it ever gets here make everything uninitialized + // if dispatcher shutsdown we'll get here so make everything uninitialized _dispatcher = null; if (_hook != null) { diff --git a/NTwain/Internals/WindowsHook.cs b/NTwain/Internals/WindowsHook.cs index 354d9a6..3a70247 100644 --- a/NTwain/Internals/WindowsHook.cs +++ b/NTwain/Internals/WindowsHook.cs @@ -13,10 +13,12 @@ namespace NTwain.Internals class WindowsHook : IDisposable { IDisposable _win; - WndProcHook _hook; + IWinMessageFilter _filter; - public WindowsHook(WndProcHook hook) + public WindowsHook(IWinMessageFilter filter) { + _filter = filter; + // hook into windows msg loop for old twain to post msgs. // the style values are purely guesses here with // CS_NOCLOSE, WS_DISABLED, and WS_EX_NOACTIVATE @@ -27,7 +29,6 @@ namespace NTwain.Internals Handle = win.Handle; win.AddHook(WndProc); _win = win; - _hook = hook; } catch { @@ -36,15 +37,12 @@ namespace NTwain.Internals } } - public delegate void WndProcHook(ref MESSAGE winMsg, ref bool handled); - private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { - if (_hook != null) + if (_filter != null) { - var winmsg = new MESSAGE(hwnd, msg, wParam, lParam); - _hook(ref winmsg, ref handled); + handled = _filter.IsTwainMessage(hwnd, msg, wParam, lParam); } return IntPtr.Zero; } @@ -52,32 +50,6 @@ namespace NTwain.Internals public IntPtr Handle { get; private set; } - /// - /// The MSG structure in Windows for TWAIN use. - /// - [StructLayout(LayoutKind.Sequential)] - public struct MESSAGE - { - public MESSAGE(IntPtr hwnd, int message, IntPtr wParam, IntPtr lParam) - { - _hwnd = hwnd; - _message = (uint)message; - _wParam = wParam; - _lParam = lParam; - _time = 0; - _x = 0; - _y = 0; - } - - IntPtr _hwnd; - uint _message; - IntPtr _wParam; - IntPtr _lParam; - uint _time; - int _x; - int _y; - } - #region IDisposable Members public void Dispose() diff --git a/NTwain/NTwain.csproj b/NTwain/NTwain.csproj index 3546a54..7e81186 100644 --- a/NTwain/NTwain.csproj +++ b/NTwain/NTwain.csproj @@ -61,6 +61,7 @@ + @@ -68,6 +69,7 @@ + True diff --git a/NTwain/Properties/VersionInfo.cs b/NTwain/Properties/VersionInfo.cs index e4b19a8..8c62ff0 100644 --- a/NTwain/Properties/VersionInfo.cs +++ b/NTwain/Properties/VersionInfo.cs @@ -12,8 +12,8 @@ namespace NTwain class _NTwainVersionInfo { // keep this same in majors releases - public const string Release = "1.0.0.0"; + public const string Release = "2.0.0.0"; // change this for each nuget release - public const string Build = "1.0.2"; + public const string Build = "2.0.0"; } } \ No newline at end of file diff --git a/NTwain/TwainSession.cs b/NTwain/TwainSession.cs index 9c0e4e9..69de05f 100644 --- a/NTwain/TwainSession.cs +++ b/NTwain/TwainSession.cs @@ -17,7 +17,7 @@ namespace NTwain /// /// Basic class for interfacing with TWAIN. You should only have one of this per application process. /// - public class TwainSession : ITwainSessionInternal + public class TwainSession : ITwainSessionInternal, IWinMessageFilter { /// /// Initializes a new instance of the class. @@ -42,13 +42,14 @@ namespace NTwain ((ITwainSessionInternal)this).ChangeState(1, false); EnforceState = true; - MessageLoop.Instance.EnsureStarted(HandleWndProcMessage); + _selfMsgLoop = new MessageLoop(); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] object _callbackObj; // kept around so it doesn't get gc'ed TWIdentity _appId; TWUserInterface _twui; + static readonly Dictionary __ownedSources = new Dictionary(); @@ -62,8 +63,6 @@ namespace NTwain return __ownedSources[key] = new TwainSource(session, sourceId); } - - /// /// Gets or sets the optional synchronization context. /// This allows events to be raised on the thread @@ -77,6 +76,9 @@ namespace NTwain #region ITwainSessionInternal Members + MessageLoop _selfMsgLoop; + MessageLoop ITwainSessionInternal.SelfMessageLoop { get { return _selfMsgLoop; } } + /// /// Gets the app id used for the session. /// @@ -126,6 +128,46 @@ namespace NTwain SafeSyncableRaiseOnEvent(OnTransferReady, TransferReady, e); } + DGAudio _dgAudio; + DGAudio ITwainSessionInternal.DGAudio + { + get + { + if (_dgAudio == null) { _dgAudio = new DGAudio(this); } + return _dgAudio; + } + } + + DGControl _dgControl; + DGControl ITwainSessionInternal.DGControl + { + get + { + if (_dgControl == null) { _dgControl = new DGControl(this); } + return _dgControl; + } + } + + DGImage _dgImage; + DGImage ITwainSessionInternal.DGImage + { + get + { + if (_dgImage == null) { _dgImage = new DGImage(this); } + return _dgImage; + } + } + + DGCustom _dgCustom; + DGCustom ITwainSessionInternal.DGCustom + { + get + { + if (_dgCustom == null) { _dgCustom = new DGCustom(this); } + return _dgCustom; + } + } + #endregion #region ITwainSession Members @@ -202,53 +244,6 @@ namespace NTwain } } - #endregion - - #region ITwainOperation Members - - DGAudio _dgAudio; - /// - /// Gets the triplet operations defined for audio data group. - /// - DGAudio ITwainSessionInternal.DGAudio - { - get - { - if (_dgAudio == null) { _dgAudio = new DGAudio(this); } - return _dgAudio; - } - } - - DGControl _dgControl; - DGControl ITwainSessionInternal.DGControl - { - get - { - if (_dgControl == null) { _dgControl = new DGControl(this); } - return _dgControl; - } - } - - DGImage _dgImage; - DGImage ITwainSessionInternal.DGImage - { - get - { - if (_dgImage == null) { _dgImage = new DGImage(this); } - return _dgImage; - } - } - - DGCustom _dgCustom; - DGCustom ITwainSessionInternal.DGCustom - { - get - { - if (_dgCustom == null) { _dgCustom = new DGCustom(this); } - return _dgCustom; - } - } - /// /// Opens the data source manager. This must be the first method used /// before using other TWAIN functions. Calls to this must be followed by when done with a TWAIN session. @@ -256,12 +251,13 @@ namespace NTwain /// public ReturnCode Open() { + _selfMsgLoop.Start(this); var rc = ReturnCode.Failure; - MessageLoop.Instance.Invoke(() => + _selfMsgLoop.Invoke(() => { Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: OpenManager.", Thread.CurrentThread.ManagedThreadId)); - rc = ((ITwainSessionInternal)this).DGControl.Parent.OpenDsm(MessageLoop.Instance.LoopHandle); + rc = ((ITwainSessionInternal)this).DGControl.Parent.OpenDsm(_selfMsgLoop.LoopHandle); if (rc == ReturnCode.Success) { // if twain2 then get memory management functions @@ -291,14 +287,15 @@ namespace NTwain public ReturnCode Close() { var rc = ReturnCode.Failure; - MessageLoop.Instance.Invoke(() => + _selfMsgLoop.Invoke(() => { Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: CloseManager.", Thread.CurrentThread.ManagedThreadId)); - rc = ((ITwainSessionInternal)this).DGControl.Parent.CloseDsm(MessageLoop.Instance.LoopHandle); + rc = ((ITwainSessionInternal)this).DGControl.Parent.CloseDsm(_selfMsgLoop.LoopHandle); if (rc == ReturnCode.Success) { Platform.MemoryManager = null; + _selfMsgLoop.Stop(); } }); return rc; @@ -399,7 +396,7 @@ namespace NTwain { var rc = ReturnCode.Failure; - MessageLoop.Instance.Invoke(() => + _selfMsgLoop.Invoke(() => { Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: EnableSource.", Thread.CurrentThread.ManagedThreadId)); @@ -454,7 +451,7 @@ namespace NTwain { var rc = ReturnCode.Failure; - MessageLoop.Instance.Invoke(() => + _selfMsgLoop.Invoke(() => { Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: DisableSource.", Thread.CurrentThread.ManagedThreadId)); @@ -495,7 +492,7 @@ namespace NTwain // success, the return status from DG_CONTROL / DAT_PENDINGXFERS / MSG_ENDXFER may // be ignored. - MessageLoop.Instance.Invoke(() => + _selfMsgLoop.Invoke(() => { if (targetState < 7) { @@ -668,8 +665,22 @@ namespace NTwain #region handle twain ds message - void HandleWndProcMessage(ref WindowsHook.MESSAGE winMsg, ref bool handled) + + #region IWinMessageFilter Members + + /// + /// Checks and handle the message if it's a TWAIN message. + /// + /// The window handle. + /// The message. + /// The w parameter. + /// The l parameter. + /// + /// true if handled internally. + /// + public bool IsTwainMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam) { + bool handled = false; // this handles the message from a typical WndProc message loop and check if it's from the TWAIN source. if (_state >= 5) { @@ -677,6 +688,8 @@ namespace NTwain IntPtr msgPtr = IntPtr.Zero; try { + var winMsg = new NTwain.Internals.MESSAGE(hwnd, msg, wParam, lParam); + // no need to do another lock call when using marshal alloc msgPtr = Marshal.AllocHGlobal(Marshal.SizeOf(winMsg)); Marshal.StructureToPtr(winMsg, msgPtr, false); @@ -695,8 +708,11 @@ namespace NTwain if (msgPtr != IntPtr.Zero) { Marshal.FreeHGlobal(msgPtr); } } } + return handled; } + #endregion + ReturnCode HandleCallback(TWIdentity origin, TWIdentity destination, DataGroups dg, DataArgumentType dat, Message msg, IntPtr data) { if (origin != null && CurrentSource != null && origin.Id == CurrentSource.Identity.Id && _state >= 5) @@ -705,7 +721,7 @@ namespace NTwain // spec says we must handle this on the thread that enabled the DS. // by using the internal dispatcher this will be the case. - MessageLoop.Instance.BeginInvoke(() => + _selfMsgLoop.BeginInvoke(() => { HandleSourceMsg(msg); }); diff --git a/NTwain/TwainSource.cs b/NTwain/TwainSource.cs index 2e514d8..5e3d57a 100644 --- a/NTwain/TwainSource.cs +++ b/NTwain/TwainSource.cs @@ -32,7 +32,7 @@ namespace NTwain public ReturnCode Open() { var rc = ReturnCode.Failure; - MessageLoop.Instance.Invoke(() => + _session.SelfMessageLoop.Invoke(() => { Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: OpenSource.", Thread.CurrentThread.ManagedThreadId)); @@ -48,7 +48,7 @@ namespace NTwain public ReturnCode Close() { var rc = ReturnCode.Failure; - MessageLoop.Instance.Invoke(() => + _session.SelfMessageLoop.Invoke(() => { Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: CloseSource.", Thread.CurrentThread.ManagedThreadId)); diff --git a/Tests/Tester.WPF/ViewModels/TwainVM.cs b/Tests/Tester.WPF/ViewModels/TwainVM.cs index 39302b6..8153351 100644 --- a/Tests/Tester.WPF/ViewModels/TwainVM.cs +++ b/Tests/Tester.WPF/ViewModels/TwainVM.cs @@ -78,7 +78,7 @@ namespace Tester.WPF Format = wantFormat, FileName = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "test.tif") }; - var rc = this.DGControl.SetupFileXfer.Set(fileSetup); + var rc = this.CurrentSource.DGControl.SetupFileXfer.Set(fileSetup); } }