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)