From 61b0e893138602850e79af3629ce20ef55570c13 Mon Sep 17 00:00:00 2001 From: soukoku Date: Sat, 24 May 2014 18:39:07 -0400 Subject: [PATCH 1/3] First changeset to make internal message loop non-global. --- NTwain.Net35/NTwain.Net35.csproj | 6 + NTwain/IWinMessageFilter.cs | 26 +++++ NTwain/Internals/ITwainSessionInternal.cs | 2 + NTwain/Internals/MESSAGE.cs | 36 ++++++ NTwain/Internals/MessageLoop.cs | 19 +-- NTwain/Internals/WindowsHook.cs | 40 +------ NTwain/NTwain.csproj | 2 + NTwain/Properties/VersionInfo.cs | 4 +- NTwain/TwainSession.cs | 136 ++++++++++++---------- NTwain/TwainSource.cs | 4 +- Tests/Tester.WPF/ViewModels/TwainVM.cs | 2 +- 11 files changed, 170 insertions(+), 107 deletions(-) create mode 100644 NTwain/IWinMessageFilter.cs create mode 100644 NTwain/Internals/MESSAGE.cs 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); } } From 764c75a7a032a5de2ecea983a1d913f466512f4a Mon Sep 17 00:00:00 2001 From: soukoku Date: Sun, 25 May 2014 08:45:52 -0400 Subject: [PATCH 2/3] Changeset 2 for swappable message loop hook implementations. --- NTwain.Net35/NTwain.Net35.csproj | 15 +- NTwain/ITwainSession.cs | 13 +- NTwain/Internals/ITwainSessionInternal.cs | 2 +- NTwain/{ => Internals}/IWinMessageFilter.cs | 2 +- ...sageLoop.cs => InternalMessageLoopHook.cs} | 26 +-- NTwain/MessageLoopHooks.cs | 189 ++++++++++++++++++ NTwain/NTwain.csproj | 8 +- NTwain/TwainSession.cs | 54 +++-- NTwain/TwainSource.cs | 4 +- Tests/Tester.WPF/MainWindow.xaml.cs | 6 +- Tests/Tester.Winform/Program.cs | 26 +-- Tests/Tester.Winform/TestForm.cs | 3 + 12 files changed, 285 insertions(+), 63 deletions(-) rename NTwain/{ => Internals}/IWinMessageFilter.cs (96%) rename NTwain/Internals/{MessageLoop.cs => InternalMessageLoopHook.cs} (84%) create mode 100644 NTwain/MessageLoopHooks.cs diff --git a/NTwain.Net35/NTwain.Net35.csproj b/NTwain.Net35/NTwain.Net35.csproj index 259a426..86bd627 100644 --- a/NTwain.Net35/NTwain.Net35.csproj +++ b/NTwain.Net35/NTwain.Net35.csproj @@ -39,7 +39,9 @@ + + @@ -76,15 +78,18 @@ Internals\ICommittable.cs + + Internals\InternalMessageLoopHook.cs + Internals\ITwainSessionInternal.cs + + Internals\IWinMessageFilter.cs + Internals\MESSAGE.cs - - Internals\MessageLoop.cs - Internals\TentativeStateCommitable.cs @@ -106,8 +111,8 @@ ITwainSession.cs - - IWinMessageFilter.cs + + MessageLoopHooks.cs Platform.cs diff --git a/NTwain/ITwainSession.cs b/NTwain/ITwainSession.cs index 4bc9b3a..a1adb98 100644 --- a/NTwain/ITwainSession.cs +++ b/NTwain/ITwainSession.cs @@ -44,13 +44,24 @@ namespace NTwain /// TwainSource ShowSourceSelector(); + /// /// 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. + /// before using other TWAIN functions. Calls to this must be followed by + /// when done with a TWAIN session. /// /// ReturnCode Open(); + /// + /// 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. + /// + /// The message loop hook. + /// + ReturnCode Open(MessageLoopHook messageLoopHook); + /// /// Closes the data source manager. /// diff --git a/NTwain/Internals/ITwainSessionInternal.cs b/NTwain/Internals/ITwainSessionInternal.cs index 54ed089..0bc3efe 100644 --- a/NTwain/Internals/ITwainSessionInternal.cs +++ b/NTwain/Internals/ITwainSessionInternal.cs @@ -14,7 +14,7 @@ namespace NTwain.Internals /// TWIdentity AppId { get; } - MessageLoop SelfMessageLoop { get; } + MessageLoopHook MessageLoopHook { get; } /// /// Gets or sets a value indicating whether calls to triplets will verify the current twain session state. diff --git a/NTwain/IWinMessageFilter.cs b/NTwain/Internals/IWinMessageFilter.cs similarity index 96% rename from NTwain/IWinMessageFilter.cs rename to NTwain/Internals/IWinMessageFilter.cs index abfd76a..b8ad668 100644 --- a/NTwain/IWinMessageFilter.cs +++ b/NTwain/Internals/IWinMessageFilter.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; -namespace NTwain +namespace NTwain.Internals { /// /// Interface for checking whether messages from WndProc is a TWAIN message and is handled diff --git a/NTwain/Internals/MessageLoop.cs b/NTwain/Internals/InternalMessageLoopHook.cs similarity index 84% rename from NTwain/Internals/MessageLoop.cs rename to NTwain/Internals/InternalMessageLoopHook.cs index 885919a..28379c8 100644 --- a/NTwain/Internals/MessageLoop.cs +++ b/NTwain/Internals/InternalMessageLoopHook.cs @@ -1,28 +1,27 @@ using NTwain.Properties; -using NTwain.Triplets; using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; +using System.Text; using System.Threading; using System.Windows.Threading; namespace NTwain.Internals { - /// - /// Provides a message loop for old TWAIN to post or new TWAIN to synchronize callbacks. - /// - class MessageLoop + sealed class InternalMessageLoopHook : MessageLoopHook { Dispatcher _dispatcher; WindowsHook _hook; - public void Stop() + internal override void Stop() { if (_dispatcher != null) { _dispatcher.InvokeShutdown(); } } - public void Start(IWinMessageFilter filter) + internal override void Start(IWinMessageFilter filter) { if (_dispatcher == null) { @@ -36,6 +35,7 @@ namespace NTwain.Internals if (!Platform.IsOnMono) { _hook = new WindowsHook(filter); + Handle = _hook.Handle; } hack.Set(); Dispatcher.Run(); @@ -55,22 +55,14 @@ namespace NTwain.Internals } } - public IntPtr LoopHandle - { - get - { - return _hook == null ? IntPtr.Zero : _hook.Handle; - } - } - - public void BeginInvoke(Action action) + internal override void BeginInvoke(Action action) { if (_dispatcher == null) { throw new InvalidOperationException(Resources.MsgLoopUnavailble); } _dispatcher.BeginInvoke(DispatcherPriority.Normal, action); } - public void Invoke(Action action) + internal override void Invoke(Action action) { if (_dispatcher == null) { throw new InvalidOperationException(Resources.MsgLoopUnavailble); } diff --git a/NTwain/MessageLoopHooks.cs b/NTwain/MessageLoopHooks.cs new file mode 100644 index 0000000..449942c --- /dev/null +++ b/NTwain/MessageLoopHooks.cs @@ -0,0 +1,189 @@ +using NTwain.Internals; +using System; +using System.Threading; +using System.Windows.Interop; + +namespace NTwain +{ + /// + /// An abstract class for TWAIN to hook into windows message loops. + /// + public abstract class MessageLoopHook + { + internal IntPtr Handle { get; set; } + internal SynchronizationContext SyncContext { get; set; } + + internal abstract void Start(IWinMessageFilter filter); + internal abstract void Stop(); + + internal virtual void BeginInvoke(Action action) + { + if (SyncContext == null) + { + action(); + } + else + { + SyncContext.Post(o => + { + action(); + }, null); + } + } + internal virtual void Invoke(Action action) + { + if (SyncContext == null) + { + action(); + } + else + { + SyncContext.Send(o => + { + action(); + }, null); + } + } + + internal void ThrowInvalidOp() + { + throw new InvalidOperationException(InvalidMessage); + } + + internal virtual string InvalidMessage { get { return string.Empty; } } + } + + /// + /// A for use in winform applications. + /// + public sealed class WindowsFormsMessageLoopHook : MessageLoopHook, System.Windows.Forms.IMessageFilter + { + IWinMessageFilter _filter; + + /// + /// Initializes a new instance of the class. + /// + /// The handle to the app window. + /// A valid window handle is required. + public WindowsFormsMessageLoopHook(IntPtr hwnd) + { + if (hwnd == IntPtr.Zero) { throw new ArgumentException("A valid window handle is required."); } + + if (!System.Windows.Forms.Application.MessageLoop) + { + ThrowInvalidOp(); + } + var sync = SynchronizationContext.Current; + if (sync == null) + { + ThrowInvalidOp(); + } + Handle = hwnd; + SyncContext = sync; + } + internal override string InvalidMessage + { + get + { + return "This can only be created on the Windows Forms UI thread."; + } + } + + internal override void Start(IWinMessageFilter filter) + { + //Invoke(() => + //{ + _filter = filter; + System.Windows.Forms.Application.AddMessageFilter(this); + //}); + } + + internal override void Stop() + { + //Invoke(() => + //{ + System.Windows.Forms.Application.RemoveMessageFilter(this); + _filter = null; + //}); + } + + + #region IMessageFilter Members + + bool System.Windows.Forms.IMessageFilter.PreFilterMessage(ref System.Windows.Forms.Message m) + { + if (_filter != null) + { + return _filter.IsTwainMessage(m.HWnd, m.Msg, m.WParam, m.LParam); + } + return false; + } + + #endregion + } + + /// + /// A for use in WPF applications. + /// + public sealed class WpfMessageLoopHook : MessageLoopHook + { + HwndSource _hooker; + IWinMessageFilter _filter; + + /// + /// Initializes a new instance of the class. + /// + /// The handle to the app window. + /// A valid window handle is required. + public WpfMessageLoopHook(IntPtr hwnd) + { + if (hwnd == IntPtr.Zero) { throw new ArgumentException("A valid window handle is required."); } + + if (System.Windows.Application.Current == null || + !System.Windows.Application.Current.Dispatcher.CheckAccess()) + { + ThrowInvalidOp(); + } + var sync = SynchronizationContext.Current; + if (sync == null) + { + ThrowInvalidOp(); + } + Handle = hwnd; + SyncContext = sync; + } + internal override string InvalidMessage + { + get + { + return "This can only be created on the WPF UI thread."; + } + } + + internal override void Start(IWinMessageFilter filter) + { + _filter = filter; + _hooker = HwndSource.FromHwnd(Handle); + _hooker.AddHook(FilterMessage); + } + + private IntPtr FilterMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) + { + if (_filter != null) + { + handled = _filter.IsTwainMessage(hwnd, msg, wParam, lParam); + } + return IntPtr.Zero; + } + + internal override void Stop() + { + if (_hooker != null) + { + _hooker.RemoveHook(FilterMessage); + _hooker.Dispose(); + _hooker = null; + } + } + } +} diff --git a/NTwain/NTwain.csproj b/NTwain/NTwain.csproj index 7e81186..9aac987 100644 --- a/NTwain/NTwain.csproj +++ b/NTwain/NTwain.csproj @@ -48,7 +48,10 @@ + + + @@ -60,6 +63,7 @@ + @@ -67,9 +71,9 @@ - - + + True diff --git a/NTwain/TwainSession.cs b/NTwain/TwainSession.cs index 69de05f..253682b 100644 --- a/NTwain/TwainSession.cs +++ b/NTwain/TwainSession.cs @@ -41,15 +41,13 @@ namespace NTwain _appId = appId; ((ITwainSessionInternal)this).ChangeState(1, false); EnforceState = true; - - _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(); @@ -64,9 +62,8 @@ namespace NTwain } /// - /// Gets or sets the optional synchronization context. - /// This allows events to be raised on the thread - /// associated with the context. + /// Gets or sets the optional synchronization context when not specifying a on . + /// This allows events to be raised on the thread associated with the context. This is experimental is not recommended for use. /// /// /// The synchronization context. @@ -76,8 +73,8 @@ namespace NTwain #region ITwainSessionInternal Members - MessageLoop _selfMsgLoop; - MessageLoop ITwainSessionInternal.SelfMessageLoop { get { return _selfMsgLoop; } } + MessageLoopHook _msgLoopHook; + MessageLoopHook ITwainSessionInternal.MessageLoopHook { get { return _msgLoopHook; } } /// /// Gets the app id used for the session. @@ -246,18 +243,35 @@ namespace NTwain /// /// 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. + /// before using other TWAIN functions. Calls to this must be followed by + /// when done with a TWAIN session. /// - /// + /// + /// 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. + /// + /// The message loop hook. + /// + /// messageLoopHook + public ReturnCode Open(MessageLoopHook messageLoopHook) + { + if (messageLoopHook == null) { throw new ArgumentNullException("messageLoopHook"); } + + _msgLoopHook = messageLoopHook; + _msgLoopHook.Start(this); var rc = ReturnCode.Failure; - _selfMsgLoop.Invoke(() => + _msgLoopHook.Invoke(() => { Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: OpenManager.", Thread.CurrentThread.ManagedThreadId)); - rc = ((ITwainSessionInternal)this).DGControl.Parent.OpenDsm(_selfMsgLoop.LoopHandle); + rc = ((ITwainSessionInternal)this).DGControl.Parent.OpenDsm(_msgLoopHook.Handle); if (rc == ReturnCode.Success) { // if twain2 then get memory management functions @@ -287,15 +301,15 @@ namespace NTwain public ReturnCode Close() { var rc = ReturnCode.Failure; - _selfMsgLoop.Invoke(() => + _msgLoopHook.Invoke(() => { Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: CloseManager.", Thread.CurrentThread.ManagedThreadId)); - rc = ((ITwainSessionInternal)this).DGControl.Parent.CloseDsm(_selfMsgLoop.LoopHandle); + rc = ((ITwainSessionInternal)this).DGControl.Parent.CloseDsm(_msgLoopHook.Handle); if (rc == ReturnCode.Success) { Platform.MemoryManager = null; - _selfMsgLoop.Stop(); + _msgLoopHook.Stop(); } }); return rc; @@ -396,7 +410,7 @@ namespace NTwain { var rc = ReturnCode.Failure; - _selfMsgLoop.Invoke(() => + _msgLoopHook.Invoke(() => { Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: EnableSource.", Thread.CurrentThread.ManagedThreadId)); @@ -451,7 +465,7 @@ namespace NTwain { var rc = ReturnCode.Failure; - _selfMsgLoop.Invoke(() => + _msgLoopHook.Invoke(() => { Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: DisableSource.", Thread.CurrentThread.ManagedThreadId)); @@ -492,7 +506,7 @@ namespace NTwain // success, the return status from DG_CONTROL / DAT_PENDINGXFERS / MSG_ENDXFER may // be ignored. - _selfMsgLoop.Invoke(() => + _msgLoopHook.Invoke(() => { if (targetState < 7) { @@ -721,7 +735,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. - _selfMsgLoop.BeginInvoke(() => + _msgLoopHook.BeginInvoke(() => { HandleSourceMsg(msg); }); diff --git a/NTwain/TwainSource.cs b/NTwain/TwainSource.cs index 5e3d57a..54114a3 100644 --- a/NTwain/TwainSource.cs +++ b/NTwain/TwainSource.cs @@ -32,7 +32,7 @@ namespace NTwain public ReturnCode Open() { var rc = ReturnCode.Failure; - _session.SelfMessageLoop.Invoke(() => + _session.MessageLoopHook.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; - _session.SelfMessageLoop.Invoke(() => + _session.MessageLoopHook.Invoke(() => { Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: CloseSource.", Thread.CurrentThread.ManagedThreadId)); diff --git a/Tests/Tester.WPF/MainWindow.xaml.cs b/Tests/Tester.WPF/MainWindow.xaml.cs index 6f2ee34..72a3198 100644 --- a/Tests/Tester.WPF/MainWindow.xaml.cs +++ b/Tests/Tester.WPF/MainWindow.xaml.cs @@ -65,7 +65,11 @@ namespace Tester.WPF { base.OnSourceInitialized(e); - var rc = _twainVM.Open(); + // use this for internal msg loop + //var rc = _twainVM.Open(); + // use this to hook into current app loop + var rc = _twainVM.Open(new WpfMessageLoopHook(new WindowInteropHelper(this).Handle)); + if (rc == ReturnCode.Success) { SrcList.ItemsSource = _twainVM.GetSources().Select(s => new DSVM { DS = s }); diff --git a/Tests/Tester.Winform/Program.cs b/Tests/Tester.Winform/Program.cs index 7120af3..79ec6fa 100644 --- a/Tests/Tester.Winform/Program.cs +++ b/Tests/Tester.Winform/Program.cs @@ -3,17 +3,17 @@ using System.Windows.Forms; namespace Tester.Winform { - static class Program - { - /// - /// The main entry point for the application. - /// - [STAThread] - static void Main() - { - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new TestForm()); - } - } + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new TestForm()); + } + } } diff --git a/Tests/Tester.Winform/TestForm.cs b/Tests/Tester.Winform/TestForm.cs index 8a20504..74050bb 100644 --- a/Tests/Tester.Winform/TestForm.cs +++ b/Tests/Tester.Winform/TestForm.cs @@ -239,7 +239,10 @@ namespace Tester.Winform } if (_twain.State < 3) { + // use this for internal msg loop _twain.Open(); + // use this to hook into current app loop + //_twain.Open(new WinformMessageLoopHook(this.Handle)); } if (_twain.State >= 3) From 0c92dd6bc09056d92c27d3bf9247074c4756d9d5 Mon Sep 17 00:00:00 2001 From: soukoku Date: Sun, 25 May 2014 09:29:44 -0400 Subject: [PATCH 3/3] Updated readme --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9e0108f..b183854 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This project has these features/goals: * Targets latest TWAIN version (2.3 at this writing) * Supports all the TWAIN functions in the spec -* Hosts an internal message loop so there's no need to hook into application UI thread +* Optionally hosts an internal message loop so there's no need to hook into application UI thread The solution contains tester projects in winform, wpf, and even (gasp!) console. A nuget package is also [available here](https://www.nuget.org/packages/ntwain) @@ -97,8 +97,9 @@ At the moment this lib does not provide ways to parse transferred image data and consumers to do the conversion themselves. The winform project contains one such example for handling DIB image in native transfer using the CommonWin32 lib. -Because it hosts its own message thread, the events will be raised from another thread. -If you would like things marshalled to a UI thread then set the SynchronizationContext property +If you call session.Open() without passing a message loop hook argument, it will run an +internal message loop behind the scenes. When this happens the events will be raised from another thread. +If you would like things marshalled to a UI thread then set the experimental SynchronizationContext property to the one from the UI thread. ```