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)