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);
}
}