Merged in optional-internal-loop (pull request #2)

Update to make internal message loop optional
This commit is contained in:
Eugene Wang 2014-05-25 09:33:11 -04:00
commit 91c7e0f83e
17 changed files with 430 additions and 144 deletions

View File

@ -39,7 +39,9 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System" />
<Reference Include="System.Windows.Forms" />
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
@ -76,11 +78,17 @@
<Compile Include="..\NTwain\Internals\ICommittable.cs">
<Link>Internals\ICommittable.cs</Link>
</Compile>
<Compile Include="..\NTwain\Internals\InternalMessageLoopHook.cs">
<Link>Internals\InternalMessageLoopHook.cs</Link>
</Compile>
<Compile Include="..\NTwain\Internals\ITwainSessionInternal.cs">
<Link>Internals\ITwainSessionInternal.cs</Link>
</Compile>
<Compile Include="..\NTwain\Internals\MessageLoop.cs">
<Link>Internals\MessageLoop.cs</Link>
<Compile Include="..\NTwain\Internals\IWinMessageFilter.cs">
<Link>Internals\IWinMessageFilter.cs</Link>
</Compile>
<Compile Include="..\NTwain\Internals\MESSAGE.cs">
<Link>Internals\MESSAGE.cs</Link>
</Compile>
<Compile Include="..\NTwain\Internals\TentativeStateCommitable.cs">
<Link>Internals\TentativeStateCommitable.cs</Link>
@ -103,6 +111,9 @@
<Compile Include="..\NTwain\ITwainSession.cs">
<Link>ITwainSession.cs</Link>
</Compile>
<Compile Include="..\NTwain\MessageLoopHooks.cs">
<Link>MessageLoopHooks.cs</Link>
</Compile>
<Compile Include="..\NTwain\Platform.cs">
<Link>Platform.cs</Link>
</Compile>

View File

@ -44,13 +44,24 @@ namespace NTwain
/// <returns></returns>
TwainSource ShowSourceSelector();
/// <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.
/// before using other TWAIN functions. Calls to this must be followed by
/// <see cref="Close" /> when done with a TWAIN session.
/// </summary>
/// <returns></returns>
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>
/// Closes the data source manager.
/// </summary>

View File

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

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NTwain.Internals
{
/// <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

@ -1,29 +1,31 @@
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
{
/// <summary>
/// Provides a message loop for old TWAIN to post or new TWAIN to synchronize callbacks.
/// </summary>
class MessageLoop
sealed class InternalMessageLoopHook : MessageLoopHook
{
static MessageLoop _instance = new MessageLoop();
public static MessageLoop Instance { get { return _instance; } }
Dispatcher _dispatcher;
WindowsHook _hook;
private MessageLoop() { }
public void EnsureStarted(WindowsHook.WndProcHook hook)
internal override void Stop()
{
if (_dispatcher != null)
{
_dispatcher.InvokeShutdown();
}
}
internal override 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 +34,12 @@ namespace NTwain.Internals
_dispatcher = Dispatcher.CurrentDispatcher;
if (!Platform.IsOnMono)
{
_hook = new WindowsHook(hook);
_hook = new WindowsHook(filter);
Handle = _hook.Handle;
}
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)
{
@ -52,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); }

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

@ -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; }
/// <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
public void Dispose()

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>
<ItemGroup>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xaml" />
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
@ -60,14 +63,17 @@
<Compile Include="DataTransferredEventArgs.cs" />
<Compile Include="IMemoryManager.cs" />
<Compile Include="Internals\ICommittable.cs" />
<Compile Include="Internals\InternalMessageLoopHook.cs" />
<Compile Include="Internals\ITwainSessionInternal.cs" />
<Compile Include="Internals\MESSAGE.cs" />
<Compile Include="Internals\TransferLogic.cs" />
<Compile Include="Internals\WindowsHook.cs" />
<Compile Include="Internals\WrappedManualResetEvent.cs" />
<Compile Include="ITwainSession.cs" />
<Compile Include="Internals\WinMemoryManager.cs" />
<Compile Include="Internals\MessageLoop.cs" />
<Compile Include="Internals\UnsafeNativeMethods.cs" />
<Compile Include="Internals\IWinMessageFilter.cs" />
<Compile Include="MessageLoopHooks.cs" />
<Compile Include="Platform.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>

View File

@ -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";
}
}

View File

@ -17,7 +17,7 @@ namespace NTwain
/// <summary>
/// Basic class for interfacing with TWAIN. You should only have one of this per application process.
/// </summary>
public class TwainSession : ITwainSessionInternal
public class TwainSession : ITwainSessionInternal, IWinMessageFilter
{
/// <summary>
/// Initializes a new instance of the <see cref="TwainSession"/> class.
@ -41,8 +41,6 @@ namespace NTwain
_appId = appId;
((ITwainSessionInternal)this).ChangeState(1, false);
EnforceState = true;
MessageLoop.Instance.EnsureStarted(HandleWndProcMessage);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
@ -50,6 +48,7 @@ namespace NTwain
TWIdentity _appId;
TWUserInterface _twui;
static readonly Dictionary<string, TwainSource> __ownedSources = new Dictionary<string, TwainSource>();
internal static TwainSource GetSourceInstance(ITwainSessionInternal session, TWIdentity sourceId)
@ -62,12 +61,9 @@ namespace NTwain
return __ownedSources[key] = new TwainSource(session, sourceId);
}
/// <summary>
/// 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 <see cref="MessageLoopHook"/> on <see cref="Open"/>.
/// This allows events to be raised on the thread associated with the context. This is experimental is not recommended for use.
/// </summary>
/// <value>
/// The synchronization context.
@ -77,6 +73,9 @@ namespace NTwain
#region ITwainSessionInternal Members
MessageLoopHook _msgLoopHook;
MessageLoopHook ITwainSessionInternal.MessageLoopHook { get { return _msgLoopHook; } }
/// <summary>
/// Gets the app id used for the session.
/// </summary>
@ -126,6 +125,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,66 +241,37 @@ namespace NTwain
}
}
#endregion
#region ITwainOperation Members
DGAudio _dgAudio;
/// <summary>
/// Gets the triplet operations defined for audio data group.
/// 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>
DGAudio ITwainSessionInternal.DGAudio
/// <returns></returns
public ReturnCode Open()
{
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;
}
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.
/// 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>
public ReturnCode Open()
/// <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;
MessageLoop.Instance.Invoke(() =>
_msgLoopHook.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(_msgLoopHook.Handle);
if (rc == ReturnCode.Success)
{
// if twain2 then get memory management functions
@ -291,14 +301,15 @@ namespace NTwain
public ReturnCode Close()
{
var rc = ReturnCode.Failure;
MessageLoop.Instance.Invoke(() =>
_msgLoopHook.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(_msgLoopHook.Handle);
if (rc == ReturnCode.Success)
{
Platform.MemoryManager = null;
_msgLoopHook.Stop();
}
});
return rc;
@ -399,7 +410,7 @@ namespace NTwain
{
var rc = ReturnCode.Failure;
MessageLoop.Instance.Invoke(() =>
_msgLoopHook.Invoke(() =>
{
Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: EnableSource.", Thread.CurrentThread.ManagedThreadId));
@ -454,7 +465,7 @@ namespace NTwain
{
var rc = ReturnCode.Failure;
MessageLoop.Instance.Invoke(() =>
_msgLoopHook.Invoke(() =>
{
Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: DisableSource.", Thread.CurrentThread.ManagedThreadId));
@ -495,7 +506,7 @@ namespace NTwain
// success, the return status from DG_CONTROL / DAT_PENDINGXFERS / MSG_ENDXFER may
// be ignored.
MessageLoop.Instance.Invoke(() =>
_msgLoopHook.Invoke(() =>
{
if (targetState < 7)
{
@ -668,8 +679,22 @@ namespace NTwain
#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.
if (_state >= 5)
{
@ -677,6 +702,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 +722,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 +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.
MessageLoop.Instance.BeginInvoke(() =>
_msgLoopHook.BeginInvoke(() =>
{
HandleSourceMsg(msg);
});

View File

@ -32,7 +32,7 @@ namespace NTwain
public ReturnCode Open()
{
var rc = ReturnCode.Failure;
MessageLoop.Instance.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;
MessageLoop.Instance.Invoke(() =>
_session.MessageLoopHook.Invoke(() =>
{
Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "Thread {0}: CloseSource.", Thread.CurrentThread.ManagedThreadId));

View File

@ -8,7 +8,7 @@ This project has these features/goals:
* Targets latest TWAIN version (2.3 at this writing)
* Supports all the TWAIN functions in the spec
* Hosts an internal message loop so there's no need to hook into application UI thread
* Optionally hosts an internal message loop so there's no need to hook into application UI thread
The solution contains tester projects in winform, wpf, and even (gasp!) console.
A nuget package is also [available here](https://www.nuget.org/packages/ntwain)
@ -97,8 +97,9 @@ At the moment this lib does not provide ways to parse transferred image data and
consumers to do the conversion themselves. The winform project contains one such
example for handling DIB image in native transfer using the CommonWin32 lib.
Because it hosts its own message thread, the events will be raised from another thread.
If you would like things marshalled to a UI thread then set the SynchronizationContext property
If you call session.Open() without passing a message loop hook argument, it will run an
internal message loop behind the scenes. When this happens the events will be raised from another thread.
If you would like things marshalled to a UI thread then set the experimental SynchronizationContext property
to the one from the UI thread.
```

View File

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

View File

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

View File

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

View File

@ -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)