First changeset to make internal message loop non-global.

This commit is contained in:
soukoku 2014-05-24 18:39:07 -04:00
parent f69698f90a
commit 61b0e89313
11 changed files with 170 additions and 107 deletions

View File

@ -79,6 +79,9 @@
<Compile Include="..\NTwain\Internals\ITwainSessionInternal.cs"> <Compile Include="..\NTwain\Internals\ITwainSessionInternal.cs">
<Link>Internals\ITwainSessionInternal.cs</Link> <Link>Internals\ITwainSessionInternal.cs</Link>
</Compile> </Compile>
<Compile Include="..\NTwain\Internals\MESSAGE.cs">
<Link>Internals\MESSAGE.cs</Link>
</Compile>
<Compile Include="..\NTwain\Internals\MessageLoop.cs"> <Compile Include="..\NTwain\Internals\MessageLoop.cs">
<Link>Internals\MessageLoop.cs</Link> <Link>Internals\MessageLoop.cs</Link>
</Compile> </Compile>
@ -103,6 +106,9 @@
<Compile Include="..\NTwain\ITwainSession.cs"> <Compile Include="..\NTwain\ITwainSession.cs">
<Link>ITwainSession.cs</Link> <Link>ITwainSession.cs</Link>
</Compile> </Compile>
<Compile Include="..\NTwain\IWinMessageFilter.cs">
<Link>IWinMessageFilter.cs</Link>
</Compile>
<Compile Include="..\NTwain\Platform.cs"> <Compile Include="..\NTwain\Platform.cs">
<Link>Platform.cs</Link> <Link>Platform.cs</Link>
</Compile> </Compile>

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NTwain
{
/// <summary>
/// Interface for checking whether messages from WndProc is a TWAIN message and is handled
/// internally.
/// </summary>
interface IWinMessageFilter
{
/// <summary>
/// Checks and handle the message if it's a TWAIN message.
/// </summary>
/// <param name="hwnd">The window handle.</param>
/// <param name="msg">The message.</param>
/// <param name="wParam">The w parameter.</param>
/// <param name="lParam">The l parameter.</param>
/// <returns>true if handled internally.</returns>
bool IsTwainMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam);
}
}

View File

@ -14,6 +14,8 @@ namespace NTwain.Internals
/// <returns></returns> /// <returns></returns>
TWIdentity AppId { get; } TWIdentity AppId { get; }
MessageLoop SelfMessageLoop { get; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether calls to triplets will verify the current twain session state. /// Gets or sets a value indicating whether calls to triplets will verify the current twain session state.
/// </summary> /// </summary>

View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace NTwain.Internals
{
/// <summary>
/// The MSG structure in Windows for TWAIN use.
/// </summary>
[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;
}
}

View File

@ -12,18 +12,21 @@ namespace NTwain.Internals
/// </summary> /// </summary>
class MessageLoop class MessageLoop
{ {
static MessageLoop _instance = new MessageLoop();
public static MessageLoop Instance { get { return _instance; } }
Dispatcher _dispatcher; Dispatcher _dispatcher;
WindowsHook _hook; 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) 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()) using (var hack = new WrappedManualResetEvent())
{ {
var loopThread = new Thread(new ThreadStart(() => var loopThread = new Thread(new ThreadStart(() =>
@ -32,11 +35,11 @@ namespace NTwain.Internals
_dispatcher = Dispatcher.CurrentDispatcher; _dispatcher = Dispatcher.CurrentDispatcher;
if (!Platform.IsOnMono) if (!Platform.IsOnMono)
{ {
_hook = new WindowsHook(hook); _hook = new WindowsHook(filter);
} }
hack.Set(); hack.Set();
Dispatcher.Run(); 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; _dispatcher = null;
if (_hook != null) if (_hook != null)
{ {

View File

@ -13,10 +13,12 @@ namespace NTwain.Internals
class WindowsHook : IDisposable class WindowsHook : IDisposable
{ {
IDisposable _win; 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. // hook into windows msg loop for old twain to post msgs.
// the style values are purely guesses here with // the style values are purely guesses here with
// CS_NOCLOSE, WS_DISABLED, and WS_EX_NOACTIVATE // CS_NOCLOSE, WS_DISABLED, and WS_EX_NOACTIVATE
@ -27,7 +29,6 @@ namespace NTwain.Internals
Handle = win.Handle; Handle = win.Handle;
win.AddHook(WndProc); win.AddHook(WndProc);
_win = win; _win = win;
_hook = hook;
} }
catch 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) 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); handled = _filter.IsTwainMessage(hwnd, msg, wParam, lParam);
_hook(ref winmsg, ref handled);
} }
return IntPtr.Zero; return IntPtr.Zero;
} }
@ -52,32 +50,6 @@ namespace NTwain.Internals
public IntPtr Handle { get; private set; } public IntPtr Handle { get; private set; }
/// <summary>
/// The MSG structure in Windows for TWAIN use.
/// </summary>
[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 #region IDisposable Members
public void Dispose() public void Dispose()

View File

@ -61,6 +61,7 @@
<Compile Include="IMemoryManager.cs" /> <Compile Include="IMemoryManager.cs" />
<Compile Include="Internals\ICommittable.cs" /> <Compile Include="Internals\ICommittable.cs" />
<Compile Include="Internals\ITwainSessionInternal.cs" /> <Compile Include="Internals\ITwainSessionInternal.cs" />
<Compile Include="Internals\MESSAGE.cs" />
<Compile Include="Internals\TransferLogic.cs" /> <Compile Include="Internals\TransferLogic.cs" />
<Compile Include="Internals\WindowsHook.cs" /> <Compile Include="Internals\WindowsHook.cs" />
<Compile Include="Internals\WrappedManualResetEvent.cs" /> <Compile Include="Internals\WrappedManualResetEvent.cs" />
@ -68,6 +69,7 @@
<Compile Include="Internals\WinMemoryManager.cs" /> <Compile Include="Internals\WinMemoryManager.cs" />
<Compile Include="Internals\MessageLoop.cs" /> <Compile Include="Internals\MessageLoop.cs" />
<Compile Include="Internals\UnsafeNativeMethods.cs" /> <Compile Include="Internals\UnsafeNativeMethods.cs" />
<Compile Include="IWinMessageFilter.cs" />
<Compile Include="Platform.cs" /> <Compile Include="Platform.cs" />
<Compile Include="Properties\Resources.Designer.cs"> <Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>

View File

@ -12,8 +12,8 @@ namespace NTwain
class _NTwainVersionInfo class _NTwainVersionInfo
{ {
// keep this same in majors releases // 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 // change this for each nuget release
public const string Build = "1.0.2"; public const string Build = "2.0.0";
} }
} }

View File

@ -17,7 +17,7 @@ namespace NTwain
/// <summary> /// <summary>
/// Basic class for interfacing with TWAIN. You should only have one of this per application process. /// Basic class for interfacing with TWAIN. You should only have one of this per application process.
/// </summary> /// </summary>
public class TwainSession : ITwainSessionInternal public class TwainSession : ITwainSessionInternal, IWinMessageFilter
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TwainSession"/> class. /// Initializes a new instance of the <see cref="TwainSession"/> class.
@ -42,13 +42,14 @@ namespace NTwain
((ITwainSessionInternal)this).ChangeState(1, false); ((ITwainSessionInternal)this).ChangeState(1, false);
EnforceState = true; EnforceState = true;
MessageLoop.Instance.EnsureStarted(HandleWndProcMessage); _selfMsgLoop = new MessageLoop();
} }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
object _callbackObj; // kept around so it doesn't get gc'ed object _callbackObj; // kept around so it doesn't get gc'ed
TWIdentity _appId; TWIdentity _appId;
TWUserInterface _twui; TWUserInterface _twui;
static readonly Dictionary<string, TwainSource> __ownedSources = new Dictionary<string, TwainSource>(); static readonly Dictionary<string, TwainSource> __ownedSources = new Dictionary<string, TwainSource>();
@ -62,8 +63,6 @@ namespace NTwain
return __ownedSources[key] = new TwainSource(session, sourceId); return __ownedSources[key] = new TwainSource(session, sourceId);
} }
/// <summary> /// <summary>
/// Gets or sets the optional synchronization context. /// Gets or sets the optional synchronization context.
/// This allows events to be raised on the thread /// This allows events to be raised on the thread
@ -77,6 +76,9 @@ namespace NTwain
#region ITwainSessionInternal Members #region ITwainSessionInternal Members
MessageLoop _selfMsgLoop;
MessageLoop ITwainSessionInternal.SelfMessageLoop { get { return _selfMsgLoop; } }
/// <summary> /// <summary>
/// Gets the app id used for the session. /// Gets the app id used for the session.
/// </summary> /// </summary>
@ -126,6 +128,46 @@ namespace NTwain
SafeSyncableRaiseOnEvent(OnTransferReady, TransferReady, e); 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 #endregion
#region ITwainSession Members #region ITwainSession Members
@ -202,53 +244,6 @@ namespace NTwain
} }
} }
#endregion
#region ITwainOperation Members
DGAudio _dgAudio;
/// <summary>
/// Gets the triplet operations defined for audio data group.
/// </summary>
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;
}
}
/// <summary> /// <summary>
/// Opens the data source manager. This must be the first method used /// Opens the data source manager. This must be the first method used
/// before using other TWAIN functions. Calls to this must be followed by <see cref="Close"/> when done with a TWAIN session. /// before using other TWAIN functions. Calls to this must be followed by <see cref="Close"/> when done with a TWAIN session.
@ -256,12 +251,13 @@ namespace NTwain
/// <returns></returns> /// <returns></returns>
public ReturnCode Open() public ReturnCode Open()
{ {
_selfMsgLoop.Start(this);
var rc = ReturnCode.Failure; var rc = ReturnCode.Failure;
MessageLoop.Instance.Invoke(() => _selfMsgLoop.Invoke(() =>
{ {
Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: OpenManager.", Thread.CurrentThread.ManagedThreadId)); 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 (rc == ReturnCode.Success)
{ {
// if twain2 then get memory management functions // if twain2 then get memory management functions
@ -291,14 +287,15 @@ namespace NTwain
public ReturnCode Close() public ReturnCode Close()
{ {
var rc = ReturnCode.Failure; var rc = ReturnCode.Failure;
MessageLoop.Instance.Invoke(() => _selfMsgLoop.Invoke(() =>
{ {
Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: CloseManager.", Thread.CurrentThread.ManagedThreadId)); 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) if (rc == ReturnCode.Success)
{ {
Platform.MemoryManager = null; Platform.MemoryManager = null;
_selfMsgLoop.Stop();
} }
}); });
return rc; return rc;
@ -399,7 +396,7 @@ namespace NTwain
{ {
var rc = ReturnCode.Failure; var rc = ReturnCode.Failure;
MessageLoop.Instance.Invoke(() => _selfMsgLoop.Invoke(() =>
{ {
Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: EnableSource.", Thread.CurrentThread.ManagedThreadId)); Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: EnableSource.", Thread.CurrentThread.ManagedThreadId));
@ -454,7 +451,7 @@ namespace NTwain
{ {
var rc = ReturnCode.Failure; var rc = ReturnCode.Failure;
MessageLoop.Instance.Invoke(() => _selfMsgLoop.Invoke(() =>
{ {
Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: DisableSource.", Thread.CurrentThread.ManagedThreadId)); 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 // success, the return status from DG_CONTROL / DAT_PENDINGXFERS / MSG_ENDXFER may
// be ignored. // be ignored.
MessageLoop.Instance.Invoke(() => _selfMsgLoop.Invoke(() =>
{ {
if (targetState < 7) if (targetState < 7)
{ {
@ -668,8 +665,22 @@ namespace NTwain
#region handle twain ds message #region handle twain ds message
void HandleWndProcMessage(ref WindowsHook.MESSAGE winMsg, ref bool handled)
#region IWinMessageFilter Members
/// <summary>
/// Checks and handle the message if it's a TWAIN message.
/// </summary>
/// <param name="hwnd">The window handle.</param>
/// <param name="msg">The message.</param>
/// <param name="wParam">The w parameter.</param>
/// <param name="lParam">The l parameter.</param>
/// <returns>
/// true if handled internally.
/// </returns>
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. // this handles the message from a typical WndProc message loop and check if it's from the TWAIN source.
if (_state >= 5) if (_state >= 5)
{ {
@ -677,6 +688,8 @@ namespace NTwain
IntPtr msgPtr = IntPtr.Zero; IntPtr msgPtr = IntPtr.Zero;
try try
{ {
var winMsg = new NTwain.Internals.MESSAGE(hwnd, msg, wParam, lParam);
// no need to do another lock call when using marshal alloc // no need to do another lock call when using marshal alloc
msgPtr = Marshal.AllocHGlobal(Marshal.SizeOf(winMsg)); msgPtr = Marshal.AllocHGlobal(Marshal.SizeOf(winMsg));
Marshal.StructureToPtr(winMsg, msgPtr, false); Marshal.StructureToPtr(winMsg, msgPtr, false);
@ -695,8 +708,11 @@ namespace NTwain
if (msgPtr != IntPtr.Zero) { Marshal.FreeHGlobal(msgPtr); } if (msgPtr != IntPtr.Zero) { Marshal.FreeHGlobal(msgPtr); }
} }
} }
return handled;
} }
#endregion
ReturnCode HandleCallback(TWIdentity origin, TWIdentity destination, DataGroups dg, DataArgumentType dat, Message msg, IntPtr data) 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) 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. // spec says we must handle this on the thread that enabled the DS.
// by using the internal dispatcher this will be the case. // by using the internal dispatcher this will be the case.
MessageLoop.Instance.BeginInvoke(() => _selfMsgLoop.BeginInvoke(() =>
{ {
HandleSourceMsg(msg); HandleSourceMsg(msg);
}); });

View File

@ -32,7 +32,7 @@ namespace NTwain
public ReturnCode Open() public ReturnCode Open()
{ {
var rc = ReturnCode.Failure; var rc = ReturnCode.Failure;
MessageLoop.Instance.Invoke(() => _session.SelfMessageLoop.Invoke(() =>
{ {
Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: OpenSource.", Thread.CurrentThread.ManagedThreadId)); Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: OpenSource.", Thread.CurrentThread.ManagedThreadId));
@ -48,7 +48,7 @@ namespace NTwain
public ReturnCode Close() public ReturnCode Close()
{ {
var rc = ReturnCode.Failure; var rc = ReturnCode.Failure;
MessageLoop.Instance.Invoke(() => _session.SelfMessageLoop.Invoke(() =>
{ {
Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: CloseSource.", Thread.CurrentThread.ManagedThreadId)); Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: CloseSource.", Thread.CurrentThread.ManagedThreadId));

View File

@ -78,7 +78,7 @@ namespace Tester.WPF
Format = wantFormat, Format = wantFormat,
FileName = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "test.tif") 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);
} }
} }