Changeset 2 for swappable message loop hook implementations.

This commit is contained in:
soukoku
2014-05-25 08:45:52 -04:00
parent 61b0e89313
commit 764c75a7a0
12 changed files with 285 additions and 63 deletions

View File

@@ -39,7 +39,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="PresentationCore" /> <Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Windows.Forms" />
<Reference Include="WindowsBase" /> <Reference Include="WindowsBase" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -76,15 +78,18 @@
<Compile Include="..\NTwain\Internals\ICommittable.cs"> <Compile Include="..\NTwain\Internals\ICommittable.cs">
<Link>Internals\ICommittable.cs</Link> <Link>Internals\ICommittable.cs</Link>
</Compile> </Compile>
<Compile Include="..\NTwain\Internals\InternalMessageLoopHook.cs">
<Link>Internals\InternalMessageLoopHook.cs</Link>
</Compile>
<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\IWinMessageFilter.cs">
<Link>Internals\IWinMessageFilter.cs</Link>
</Compile>
<Compile Include="..\NTwain\Internals\MESSAGE.cs"> <Compile Include="..\NTwain\Internals\MESSAGE.cs">
<Link>Internals\MESSAGE.cs</Link> <Link>Internals\MESSAGE.cs</Link>
</Compile> </Compile>
<Compile Include="..\NTwain\Internals\MessageLoop.cs">
<Link>Internals\MessageLoop.cs</Link>
</Compile>
<Compile Include="..\NTwain\Internals\TentativeStateCommitable.cs"> <Compile Include="..\NTwain\Internals\TentativeStateCommitable.cs">
<Link>Internals\TentativeStateCommitable.cs</Link> <Link>Internals\TentativeStateCommitable.cs</Link>
</Compile> </Compile>
@@ -106,8 +111,8 @@
<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"> <Compile Include="..\NTwain\MessageLoopHooks.cs">
<Link>IWinMessageFilter.cs</Link> <Link>MessageLoopHooks.cs</Link>
</Compile> </Compile>
<Compile Include="..\NTwain\Platform.cs"> <Compile Include="..\NTwain\Platform.cs">
<Link>Platform.cs</Link> <Link>Platform.cs</Link>

View File

@@ -44,13 +44,24 @@ namespace NTwain
/// <returns></returns> /// <returns></returns>
TwainSource ShowSourceSelector(); TwainSource ShowSourceSelector();
/// <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.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
ReturnCode Open(); ReturnCode Open();
/// <summary>
/// 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.
/// </summary>
/// <param name="messageLoopHook">The message loop hook.</param>
/// <returns></returns>
ReturnCode Open(MessageLoopHook messageLoopHook);
/// <summary> /// <summary>
/// Closes the data source manager. /// Closes the data source manager.
/// </summary> /// </summary>

View File

@@ -14,7 +14,7 @@ namespace NTwain.Internals
/// <returns></returns> /// <returns></returns>
TWIdentity AppId { get; } TWIdentity AppId { get; }
MessageLoop SelfMessageLoop { get; } MessageLoopHook MessageLoopHook { 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.

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
namespace NTwain namespace NTwain.Internals
{ {
/// <summary> /// <summary>
/// Interface for checking whether messages from WndProc is a TWAIN message and is handled /// Interface for checking whether messages from WndProc is a TWAIN message and is handled

View File

@@ -1,28 +1,27 @@
using NTwain.Properties; using NTwain.Properties;
using NTwain.Triplets;
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading; using System.Threading;
using System.Windows.Threading; using System.Windows.Threading;
namespace NTwain.Internals namespace NTwain.Internals
{ {
/// <summary> sealed class InternalMessageLoopHook : MessageLoopHook
/// Provides a message loop for old TWAIN to post or new TWAIN to synchronize callbacks.
/// </summary>
class MessageLoop
{ {
Dispatcher _dispatcher; Dispatcher _dispatcher;
WindowsHook _hook; WindowsHook _hook;
public void Stop() internal override void Stop()
{ {
if (_dispatcher != null) if (_dispatcher != null)
{ {
_dispatcher.InvokeShutdown(); _dispatcher.InvokeShutdown();
} }
} }
public void Start(IWinMessageFilter filter) internal override void Start(IWinMessageFilter filter)
{ {
if (_dispatcher == null) if (_dispatcher == null)
{ {
@@ -36,6 +35,7 @@ namespace NTwain.Internals
if (!Platform.IsOnMono) if (!Platform.IsOnMono)
{ {
_hook = new WindowsHook(filter); _hook = new WindowsHook(filter);
Handle = _hook.Handle;
} }
hack.Set(); hack.Set();
Dispatcher.Run(); Dispatcher.Run();
@@ -55,22 +55,14 @@ namespace NTwain.Internals
} }
} }
public IntPtr LoopHandle internal override void BeginInvoke(Action action)
{
get
{
return _hook == null ? IntPtr.Zero : _hook.Handle;
}
}
public void BeginInvoke(Action action)
{ {
if (_dispatcher == null) { throw new InvalidOperationException(Resources.MsgLoopUnavailble); } if (_dispatcher == null) { throw new InvalidOperationException(Resources.MsgLoopUnavailble); }
_dispatcher.BeginInvoke(DispatcherPriority.Normal, action); _dispatcher.BeginInvoke(DispatcherPriority.Normal, action);
} }
public void Invoke(Action action) internal override void Invoke(Action action)
{ {
if (_dispatcher == null) { throw new InvalidOperationException(Resources.MsgLoopUnavailble); } if (_dispatcher == null) { throw new InvalidOperationException(Resources.MsgLoopUnavailble); }

189
NTwain/MessageLoopHooks.cs Normal file
View File

@@ -0,0 +1,189 @@
using NTwain.Internals;
using System;
using System.Threading;
using System.Windows.Interop;
namespace NTwain
{
/// <summary>
/// An abstract class for TWAIN to hook into windows message loops.
/// </summary>
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; } }
}
/// <summary>
/// A <see cref="MessageLoopHook"/> for use in winform applications.
/// </summary>
public sealed class WindowsFormsMessageLoopHook : MessageLoopHook, System.Windows.Forms.IMessageFilter
{
IWinMessageFilter _filter;
/// <summary>
/// Initializes a new instance of the <see cref="WindowsFormsMessageLoopHook"/> class.
/// </summary>
/// <param name="hwnd">The handle to the app window.</param>
/// <exception cref="System.ArgumentException">A valid window handle is required.</exception>
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
}
/// <summary>
/// A <see cref="MessageLoopHook"/> for use in WPF applications.
/// </summary>
public sealed class WpfMessageLoopHook : MessageLoopHook
{
HwndSource _hooker;
IWinMessageFilter _filter;
/// <summary>
/// Initializes a new instance of the <see cref="WpfMessageLoopHook"/> class.
/// </summary>
/// <param name="hwnd">The handle to the app window.</param>
/// <exception cref="System.ArgumentException">A valid window handle is required.</exception>
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;
}
}
}
}

View File

@@ -48,7 +48,10 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="PresentationCore" /> <Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xaml" />
<Reference Include="WindowsBase" /> <Reference Include="WindowsBase" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -60,6 +63,7 @@
<Compile Include="DataTransferredEventArgs.cs" /> <Compile Include="DataTransferredEventArgs.cs" />
<Compile Include="IMemoryManager.cs" /> <Compile Include="IMemoryManager.cs" />
<Compile Include="Internals\ICommittable.cs" /> <Compile Include="Internals\ICommittable.cs" />
<Compile Include="Internals\InternalMessageLoopHook.cs" />
<Compile Include="Internals\ITwainSessionInternal.cs" /> <Compile Include="Internals\ITwainSessionInternal.cs" />
<Compile Include="Internals\MESSAGE.cs" /> <Compile Include="Internals\MESSAGE.cs" />
<Compile Include="Internals\TransferLogic.cs" /> <Compile Include="Internals\TransferLogic.cs" />
@@ -67,9 +71,9 @@
<Compile Include="Internals\WrappedManualResetEvent.cs" /> <Compile Include="Internals\WrappedManualResetEvent.cs" />
<Compile Include="ITwainSession.cs" /> <Compile Include="ITwainSession.cs" />
<Compile Include="Internals\WinMemoryManager.cs" /> <Compile Include="Internals\WinMemoryManager.cs" />
<Compile Include="Internals\MessageLoop.cs" />
<Compile Include="Internals\UnsafeNativeMethods.cs" /> <Compile Include="Internals\UnsafeNativeMethods.cs" />
<Compile Include="IWinMessageFilter.cs" /> <Compile Include="Internals\IWinMessageFilter.cs" />
<Compile Include="MessageLoopHooks.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

@@ -41,15 +41,13 @@ namespace NTwain
_appId = appId; _appId = appId;
((ITwainSessionInternal)this).ChangeState(1, false); ((ITwainSessionInternal)this).ChangeState(1, false);
EnforceState = true; EnforceState = true;
_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>();
@@ -64,9 +62,8 @@ namespace NTwain
} }
/// <summary> /// <summary>
/// Gets or sets the optional synchronization context. /// Gets or sets the optional synchronization context when not specifying a <see cref="MessageLoopHook"/> on <see cref="Open"/>.
/// This allows events to be raised on the thread /// This allows events to be raised on the thread associated with the context. This is experimental is not recommended for use.
/// associated with the context.
/// </summary> /// </summary>
/// <value> /// <value>
/// The synchronization context. /// The synchronization context.
@@ -76,8 +73,8 @@ namespace NTwain
#region ITwainSessionInternal Members #region ITwainSessionInternal Members
MessageLoop _selfMsgLoop; MessageLoopHook _msgLoopHook;
MessageLoop ITwainSessionInternal.SelfMessageLoop { get { return _selfMsgLoop; } } MessageLoopHook ITwainSessionInternal.MessageLoopHook { get { return _msgLoopHook; } }
/// <summary> /// <summary>
/// Gets the app id used for the session. /// Gets the app id used for the session.
@@ -246,18 +243,35 @@ namespace NTwain
/// <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.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns
public ReturnCode Open() public ReturnCode Open()
{ {
_selfMsgLoop.Start(this); return Open(new InternalMessageLoopHook());
}
/// <summary>
/// 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.
/// </summary>
/// <param name="messageLoopHook">The message loop hook.</param>
/// <returns></returns>
/// <exception cref="System.ArgumentNullException">messageLoopHook</exception>
public ReturnCode Open(MessageLoopHook messageLoopHook)
{
if (messageLoopHook == null) { throw new ArgumentNullException("messageLoopHook"); }
_msgLoopHook = messageLoopHook;
_msgLoopHook.Start(this);
var rc = ReturnCode.Failure; var rc = ReturnCode.Failure;
_selfMsgLoop.Invoke(() => _msgLoopHook.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(_selfMsgLoop.LoopHandle); rc = ((ITwainSessionInternal)this).DGControl.Parent.OpenDsm(_msgLoopHook.Handle);
if (rc == ReturnCode.Success) if (rc == ReturnCode.Success)
{ {
// if twain2 then get memory management functions // if twain2 then get memory management functions
@@ -287,15 +301,15 @@ namespace NTwain
public ReturnCode Close() public ReturnCode Close()
{ {
var rc = ReturnCode.Failure; var rc = ReturnCode.Failure;
_selfMsgLoop.Invoke(() => _msgLoopHook.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(_selfMsgLoop.LoopHandle); rc = ((ITwainSessionInternal)this).DGControl.Parent.CloseDsm(_msgLoopHook.Handle);
if (rc == ReturnCode.Success) if (rc == ReturnCode.Success)
{ {
Platform.MemoryManager = null; Platform.MemoryManager = null;
_selfMsgLoop.Stop(); _msgLoopHook.Stop();
} }
}); });
return rc; return rc;
@@ -396,7 +410,7 @@ namespace NTwain
{ {
var rc = ReturnCode.Failure; var rc = ReturnCode.Failure;
_selfMsgLoop.Invoke(() => _msgLoopHook.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));
@@ -451,7 +465,7 @@ namespace NTwain
{ {
var rc = ReturnCode.Failure; var rc = ReturnCode.Failure;
_selfMsgLoop.Invoke(() => _msgLoopHook.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));
@@ -492,7 +506,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.
_selfMsgLoop.Invoke(() => _msgLoopHook.Invoke(() =>
{ {
if (targetState < 7) if (targetState < 7)
{ {
@@ -721,7 +735,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.
_selfMsgLoop.BeginInvoke(() => _msgLoopHook.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;
_session.SelfMessageLoop.Invoke(() => _session.MessageLoopHook.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;
_session.SelfMessageLoop.Invoke(() => _session.MessageLoopHook.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

@@ -65,7 +65,11 @@ namespace Tester.WPF
{ {
base.OnSourceInitialized(e); 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) if (rc == ReturnCode.Success)
{ {
SrcList.ItemsSource = _twainVM.GetSources().Select(s => new DSVM { DS = s }); SrcList.ItemsSource = _twainVM.GetSources().Select(s => new DSVM { DS = s });

View File

@@ -3,17 +3,17 @@ using System.Windows.Forms;
namespace Tester.Winform namespace Tester.Winform
{ {
static class Program static class Program
{ {
/// <summary> /// <summary>
/// The main entry point for the application. /// The main entry point for the application.
/// </summary> /// </summary>
[STAThread] [STAThread]
static void Main() static void Main()
{ {
Application.EnableVisualStyles(); Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false); Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new TestForm()); Application.Run(new TestForm());
} }
} }
} }

View File

@@ -239,7 +239,10 @@ namespace Tester.Winform
} }
if (_twain.State < 3) if (_twain.State < 3)
{ {
// use this for internal msg loop
_twain.Open(); _twain.Open();
// use this to hook into current app loop
//_twain.Open(new WinformMessageLoopHook(this.Handle));
} }
if (_twain.State >= 3) if (_twain.State >= 3)