Merge pull request #3 from soukoku/v4-dev

More thread marshaling stuff.
This commit is contained in:
Eugene Wang
2018-11-23 20:01:11 -05:00
committed by GitHub
23 changed files with 2952 additions and 320 deletions

View File

@@ -1,4 +1,4 @@
# TWAIN Application-Library # TWAIN dotnet library
NOTE: THIS IS V4 DEV AND DOESN'T WORK YET. NOTE: THIS IS V4 DEV AND DOESN'T WORK YET.
USE V3 BRANCH FOR WORKING VERSION. USE V3 BRANCH FOR WORKING VERSION.
@@ -10,9 +10,9 @@ This is a library created to make working with
V4 of this lib has these goals: V4 of this lib has these goals:
* Targets latest TWAIN version (2.4 as of this writing) * Targets latest TWAIN version (2.4 as of this writing)
* Supports all the TWAIN functions in the spec * Supports all the TWAIN functions in the spec (directly or through dotnet wrapper)
* Works with both 32 and 64 bit data sources * Works with both 32 or 64 bit data sources, from 32 or 64 bit apps.
* Works on non-Windows platforms using dotnet core or mono (maybe) * Works on non-Windows platforms using dotnet core
## Using the lib ## Using the lib

View File

@@ -63,16 +63,16 @@ namespace ConsoleApp
Console.WriteLine("ERROR: " + ex.ToString()); Console.WriteLine("ERROR: " + ex.ToString());
} }
Console.WriteLine("------------------------------------------------"); Console.WriteLine("---------------------------------------------");
Console.WriteLine("Test in progress, press Enter to stop testing..."); Console.WriteLine("Test in progress, press Enter to stop testing");
Console.WriteLine("------------------------------------------------"); Console.WriteLine("---------------------------------------------");
Console.ReadLine(); Console.ReadLine();
var rc = session.StepDown(NTwain.Data.TwainState.S1); var rc = session.StepDown(NTwain.Data.TwainState.S1);
Console.WriteLine("StepDown RC=" + rc); Console.WriteLine("Session StepDown RC=" + rc);
Console.WriteLine("-------------------");
Console.WriteLine("Press Enter to exit");
Console.WriteLine("-------------------");
Console.ReadLine(); Console.ReadLine();
Console.WriteLine("----------------------");
Console.WriteLine("Press Enter to exit...");
Console.WriteLine("----------------------");
} }
private static void Session_SourceDisabled(object sender, EventArgs e) private static void Session_SourceDisabled(object sender, EventArgs e)

View File

@@ -1,31 +1,62 @@
// code block to verify size of things on different platforms and archtectures // code block to verify size of things on different platforms and archtectures
#if _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#include <iostream>
#include "twain.h" #include "twain.h"
int main() int main()
{ {
bool is32 = sizeof(void *) == 4; bool is32 = sizeof(void *) == 4;
bool is64 = sizeof(void *) == 8; bool is64 = sizeof(void *) == 8;
std::cout << (is32 ? "32bit= " : "") << (is64 ? "64bit" : "") << std::endl; std::cout << (is32 ? "32bit" : "") << (is64 ? "64bit" : "") << std::endl;
std::cout << std::endl; std::cout << std::endl;
std::cout << "TW_STR32 = " << sizeof TW_STR32 << std::endl; std::cout << "TW_STR32 = " << sizeof(TW_STR32) << std::endl;
std::cout << "TW_STR64 = " << sizeof TW_STR64 << std::endl; std::cout << "TW_STR64 = " << sizeof(TW_STR64) << std::endl;
std::cout << "TW_STR128 = " << sizeof TW_STR128 << std::endl; std::cout << "TW_STR128 = " << sizeof(TW_STR128) << std::endl;
std::cout << "TW_STR255 = " << sizeof TW_STR255 << std::endl; std::cout << "TW_STR255 = " << sizeof(TW_STR255) << std::endl;
std::cout << std::endl; std::cout << std::endl;
std::cout << "TW_INT8 = " << sizeof TW_INT8 << std::endl; std::cout << "TW_INT8 = " << sizeof(TW_INT8) << std::endl;
std::cout << "TW_INT16 = " << sizeof TW_INT16 << std::endl; std::cout << "TW_INT16 = " << sizeof(TW_INT16) << std::endl;
std::cout << "TW_INT32 = " << sizeof TW_INT32 << std::endl; std::cout << "TW_INT32 = " << sizeof(TW_INT32) << std::endl;
std::cout << "TW_UINT8 = " << sizeof TW_UINT8 << std::endl; std::cout << "TW_UINT8 = " << sizeof(TW_UINT8) << std::endl;
std::cout << "TW_UINT16 = " << sizeof TW_UINT16 << std::endl; std::cout << "TW_UINT16 = " << sizeof(TW_UINT16) << std::endl;
std::cout << "TW_UINT32 = " << sizeof TW_UINT32 << std::endl; std::cout << "TW_UINT32 = " << sizeof(TW_UINT32) << std::endl;
std::cout << "TW_BOOL = " << sizeof TW_BOOL << std::endl; std::cout << "TW_BOOL = " << sizeof(TW_BOOL) << std::endl;
std::cout << std::endl; std::cout << std::endl;
std::cout << "TW_HANDLE = " << sizeof TW_HANDLE << std::endl; std::cout << "TW_IDENTITY = " << sizeof(TW_IDENTITY) << std::endl;
std::cout << "TW_MEMREF = " << sizeof TW_MEMREF << std::endl;
std::cout << "TW_UINTPTR= " << sizeof TW_UINTPTR << std::endl;
std::cout << std::endl; std::cout << std::endl;
std::cout << "TW_IDENTITY = " << sizeof TW_IDENTITY << std::endl; std::cout << "TW_HANDLE = " << sizeof(TW_HANDLE) << std::endl;
std::cout << "TW_MEMREF = " << sizeof(TW_MEMREF) << std::endl;
std::cout << "TW_UINTPTR= " << sizeof(TW_UINTPTR) << std::endl;
std::cout << std::endl;
std::cout << "TW_ONEVALUE = " << sizeof(TW_ONEVALUE) << std::endl;
std::cout << "TW_ARRAY = " << sizeof(TW_ARRAY) << std::endl;
std::cout << "TW_ENUMERATION = " << sizeof(TW_ENUMERATION) << std::endl;
std::cout << "TW_RANGE = " << sizeof(TW_RANGE) << std::endl;
std::cout << std::endl;
std::cout << "TW_GRAYRESPONSE = " << sizeof(TW_GRAYRESPONSE) << std::endl;
std::cout << "TW_RGBRESPONSE = " << sizeof(TW_RGBRESPONSE) << std::endl;
std::cout << std::endl;
std::cout << "TW_CALLBACK = " << sizeof(TW_CALLBACK) << std::endl;
std::cout << "TW_CALLBACK2 = " << sizeof(TW_CALLBACK2) << std::endl;
std::cout << std::endl;
std::cout << "TW_USERINTERFACE = " << sizeof(TW_USERINTERFACE) << std::endl;
std::cout << "TW_PENDINGXFERS = " << sizeof(TW_PENDINGXFERS) << std::endl;
std::cout << "TW_IMAGEMEMXFER = " << sizeof(TW_IMAGEMEMXFER) << std::endl;
std::cout << "TW_MEMORY = " << sizeof(TW_MEMORY) << std::endl;
std::cout << std::endl;
std::cout << "TW_CAPABILITY = " << sizeof(TW_CAPABILITY) << std::endl;
std::cout << "TW_CUSTOMDSDATA = " << sizeof(TW_CUSTOMDSDATA) << std::endl;
std::cout << "TW_EVENT = " << sizeof(TW_EVENT) << std::endl;
std::cout << "TW_INFO = " << sizeof(TW_INFO) << std::endl;
std::cout << "TW_FILESYSTEM = " << sizeof(TW_FILESYSTEM) << std::endl;
std::cout << "TW_JPEGCOMPRESSION = " << sizeof(TW_JPEGCOMPRESSION) << std::endl;
std::cout << "TW_PASSTHRU = " << sizeof(TW_PASSTHRU) << std::endl;
std::cout << "TW_STATUSUTF8 = " << sizeof(TW_STATUSUTF8) << std::endl;
std::cout << "TW_TWAINDIRECT = " << sizeof(TW_TWAINDIRECT) << std::endl;
int test; int test;
std::cin >> test; std::cin >> test;
} }

2265
size-test/twain.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NTwain.Data
{
// use custom containers for twain container types to not have to worry about memory mgmt
// after giving it to consumers
/// <summary>
/// Container for one value.
/// </summary>
/// <typeparam name="T"></typeparam>
public struct OneValue<T>
{
/// <summary>
/// The type of the item.
/// </summary>
public ItemType Type;
/// <summary>
/// The value.
/// </summary>
public T Value;
}
/// <summary>
/// Stores a group of associated individual values for a capability.
/// The values need have no relationship to one another aside from
/// being used to describe the same "value" of the capability
/// </summary>
public struct ArrayValue<T>
{
/// <summary>
/// The type of items in the array.
/// </summary>
public ItemType Type;
/// <summary>
/// Array of values.
/// </summary>
public T[] ItemList;
}
/// <summary>
/// An enumeration stores a list of individual values, with one of the items designated as the current
/// value. There is no required order to the values in the list.
/// </summary>
public struct EnumValue<T>
{
/// <summary>
/// Gets the byte offset of the item list from a Ptr to the first item.
/// </summary>
internal const int ValuesOffset = 14;
/// <summary>
/// The type of items in the enumerated list.
/// </summary>
public ItemType Type;
/// <summary>
/// The item number, or index (zero-based) into
/// <see cref="ItemList"/>, of the "current"
/// value for the capability.
/// </summary>
public int CurrentIndex;
/// <summary>
/// The item number, or index (zero-based) into
/// <see cref="ItemList"/>, of the "power-on"
/// value for the capability.
/// </summary>
public int DefaultIndex;
/// <summary>
/// The enumerated value list.
/// </summary>
public T[] ItemList;
}
/// <summary>
/// Container for a range of values.
/// </summary>
public struct RangeValue
{
/// <summary>
/// The type of items in the container.
/// </summary>
public ItemType Type;
/// <summary>
/// The least positive/most negative value of the range.
/// </summary>
public int Min;
/// <summary>
/// The most positive/least negative value of the range.
/// </summary>
public int Max;
/// <summary>
/// The delta between two adjacent values of the range.
/// e.g. Item2 - Item1 = StepSize;
/// </summary>
public int StepSize;
/// <summary>
/// The devices "power-on" value for the capability. If the application is
/// performing a MSG_SET operation and isnt sure what the default
/// value is, set this field to <see cref="TwainConst.DontCare32"/>.
/// </summary>
public int DefaultValue;
/// <summary>
/// The value to which the device (or its user interface) is currently set to
/// for the capability.
/// </summary>
public int CurrentValue;
}
}

View File

@@ -74,9 +74,9 @@ namespace NTwain.Data
partial class TW_ARRAY partial class TW_ARRAY
{ {
// TODO: _itemType in mac is TW_UINT32? // TODO: _itemType in mac is TW_UINT32?
TW_UINT16 _itemType; public TW_UINT16 ItemType;
TW_UINT32 _numItems; public TW_UINT32 NumItems;
object[] _itemList; public IntPtr ItemList;
} }
[StructLayout(LayoutKind.Sequential, Pack = 2), [StructLayout(LayoutKind.Sequential, Pack = 2),
@@ -181,12 +181,12 @@ namespace NTwain.Data
partial class TW_ENUMERATION partial class TW_ENUMERATION
{ {
// TODO: _itemType in mac is TW_UINT32? // TODO: _itemType in mac is TW_UINT32?
TW_UINT16 _itemType; public TW_UINT16 ItemType;
// TODO: these are UInt64 in linux 64 bit? // TODO: these are UInt64 in linux 64 bit?
TW_UINT32 _numItems; public TW_UINT32 NumItems;
TW_UINT32 _currentIndex; public TW_UINT32 CurrentIndex;
TW_UINT32 _defaultIndex; public TW_UINT32 DefaultIndex;
object[] _itemList; public IntPtr ItemList;
} }
[StructLayout(LayoutKind.Sequential, Pack = 2)] [StructLayout(LayoutKind.Sequential, Pack = 2)]
@@ -431,8 +431,8 @@ namespace NTwain.Data
partial class TW_ONEVALUE partial class TW_ONEVALUE
{ {
// TODO: mac is different? // TODO: mac is different?
TW_UINT16 _itemType; public TW_UINT16 ItemType;
TW_UINT32 _item; public TW_UINT32 Item;
} }
[StructLayout(LayoutKind.Sequential, Pack = 2)] [StructLayout(LayoutKind.Sequential, Pack = 2)]
@@ -468,12 +468,12 @@ namespace NTwain.Data
partial class TW_RANGE partial class TW_RANGE
{ {
// TODO: mac & linux are different? // TODO: mac & linux are different?
TW_UINT16 _itemType; public TW_UINT16 ItemType;
TW_UINT32 _minValue; public TW_UINT32 MinValue;
TW_UINT32 _maxValue; public TW_UINT32 MaxValue;
TW_UINT32 _stepSize; public TW_UINT32 StepSize;
TW_UINT32 _defaultValue; public TW_UINT32 DefaultValue;
TW_UINT32 _currentValue; public TW_UINT32 CurrentValue;
} }
//[StructLayout(LayoutKind.Sequential, Pack = 2)] //[StructLayout(LayoutKind.Sequential, Pack = 2)]

View File

@@ -526,39 +526,6 @@ namespace NTwain.Data
} }
} }
// /// <summary>
// /// Stores a group of associated individual values for a capability.
// /// The values need have no relationship to one another aside from
// /// being used to describe the same "value" of the capability
// /// </summary>
// public partial class TW_ARRAY
// {
// /// <summary>
// /// The type of items in the array. All items in the array have the same size.
// /// </summary>
// public ItemType ItemType { get { return (ItemType)_itemType; } set { _itemType = (ushort)value; } }
// ///// <summary>
// ///// How many items are in the array.
// ///// </summary>
// //public int Count { get { return (int)_numItems; } set { _numItems = (uint)value; } }
// /// <summary>
// /// Array of ItemType values starts here.
// /// </summary>
// public object[] ItemList
// {
// get { return _itemList; }
// set
// {
// _itemList = value;
// if (value != null) { _numItems = (uint)value.Length; }
// else { _numItems = 0; }
// }
// }
// }
/// <summary> /// <summary>
/// Used to get audio info. /// Used to get audio info.
/// </summary> /// </summary>
@@ -1120,52 +1087,6 @@ namespace NTwain.Data
#endregion #endregion
} }
// /// <summary>
// /// An enumeration stores a list of individual values, with one of the items designated as the current
// /// value. There is no required order to the values in the list.
// /// </summary>
// public partial class TW_ENUMERATION
// {
// /// <summary>
// /// The type of items in the enumerated list. All items in the array have the same size.
// /// </summary>
// public ItemType ItemType { get { return (ItemType)_itemType; } set { _itemType = (ushort)value; } }
// ///// <summary>
// ///// How many items are in the enumeration.
// ///// </summary>
// //public int Count { get { return (int)_numItems; } set { _numItems = (uint)value; } }
// /// <summary>
// /// The item number, or index (zero-based) into <see cref="ItemList"/>, of the "current"
// /// value for the capability.
// /// </summary>
// public int CurrentIndex { get { return (int)_currentIndex; } set { _currentIndex = (uint)value; } }
// /// <summary>
// /// The item number, or index (zero-based) into <see cref="ItemList"/>, of the "power-on"
// /// value for the capability.
// /// </summary>
// public int DefaultIndex { get { return (int)_defaultIndex; } set { _defaultIndex = (uint)value; } }
// /// <summary>
// /// The enumerated list: one value resides within each array element.
// /// </summary>
// public object[] ItemList
// {
// get { return _itemList; }
// set
// {
// _itemList = value;
// if (value != null) { _numItems = (uint)value.Length; }
// else { _numItems = 0; }
// }
// }
// /// <summary>
// /// Gets the byte offset of the item list from a Ptr to the first item.
// /// </summary>
// internal const int ItemOffset = 14;
// }
// /// <summary> // /// <summary>
// /// This structure is used to pass specific information between the data source and the application // /// This structure is used to pass specific information between the data source and the application
// /// through <see cref="TW_EXTIMAGEINFO"/>. // /// through <see cref="TW_EXTIMAGEINFO"/>.
@@ -1954,21 +1875,6 @@ namespace NTwain.Data
public uint SheetCount => _sheetCount; public uint SheetCount => _sheetCount;
} }
// /// <summary>
// /// Container for one value.
// /// </summary>
// public partial class TW_ONEVALUE
// {
// /// <summary>
// /// The type of the item.
// /// </summary>
// public ItemType ItemType { get { return (ItemType)_itemType; } set { _itemType = (ushort)value; } }
// /// <summary>
// /// The value.
// /// </summary>
// public uint Item { get { return _item; } set { _item = value; } }
// }
/// <summary> /// <summary>
/// This structure holds the color palette information for buffered memory transfers of type /// This structure holds the color palette information for buffered memory transfers of type
@@ -2053,41 +1959,6 @@ namespace NTwain.Data
} }
// /// <summary>
// /// Container for a range of values.
// /// </summary>
// public partial class TW_RANGE
// {
// /// <summary>
// /// The type of items in the list.
// /// </summary>
// public ItemType ItemType { get { return (ItemType)_itemType; } set { _itemType = (ushort)value; } }
// /// <summary>
// /// The least positive/most negative value of the range.
// /// </summary>
// public uint MinValue { get { return _minValue; } set { _minValue = value; } }
// /// <summary>
// /// The most positive/least negative value of the range.
// /// </summary>
// public uint MaxValue { get { return _maxValue; } set { _maxValue = value; } }
// /// <summary>
// /// The delta between two adjacent values of the range.
// /// e.g. Item2 - Item1 = StepSize;
// /// </summary>
// public uint StepSize { get { return _stepSize; } set { _stepSize = value; } }
// /// <summary>
// /// The devices "power-on" value for the capability. If the application is
// /// performing a MSG_SET operation and isnt sure what the default
// /// value is, set this field to <see cref="TwainConst.DontCare32"/>.
// /// </summary>
// public uint DefaultValue { get { return _defaultValue; } set { _defaultValue = value; } }
// /// <summary>
// /// The value to which the device (or its user interface) is currently set to
// /// for the capability.
// /// </summary>
// public uint CurrentValue { get { return _currentValue; } set { _currentValue = value; } }
// }
// ///// <summary> // ///// <summary>
// ///// This structure is used by the application to specify a set of mapping values to be applied to RGB // ///// This structure is used by the application to specify a set of mapping values to be applied to RGB
// ///// color data. Use this structure for RGB data whose bit depth is up to, and including, 8-bits. // ///// color data. Use this structure for RGB data whose bit depth is up to, and including, 8-bits.

View File

@@ -13,7 +13,7 @@ namespace NTwain.Data.Win32
struct MSG struct MSG
{ {
public IntPtr hwnd; public IntPtr hwnd;
public int message; public uint message;
public IntPtr wParam; public IntPtr wParam;
public IntPtr lParam; public IntPtr lParam;
int time; int time;

View File

@@ -10,7 +10,7 @@ namespace NTwain.Data.Win32
/// <summary> /// <summary>
/// Enumerated values of window messages. /// Enumerated values of window messages.
/// </summary> /// </summary>
enum WindowMessage enum WindowMessage : uint
{ {
/// <summary> /// <summary>
/// Performs no operation. An application sends the WM_NULL message if it wants to post a message that the recipient window will ignore. /// Performs no operation. An application sends the WM_NULL message if it wants to post a message that the recipient window will ignore.

View File

@@ -44,7 +44,7 @@ namespace NTwain
public ReturnCode ShowUI(IntPtr windowHandle, bool modal = false) public ReturnCode ShowUI(IntPtr windowHandle, bool modal = false)
{ {
var rc = ReturnCode.Failure; var rc = ReturnCode.Failure;
Session.Invoke(() => Session.InternalInvoke(() =>
{ {
var ui = new TW_USERINTERFACE var ui = new TW_USERINTERFACE
{ {
@@ -72,7 +72,7 @@ namespace NTwain
public ReturnCode Enable(bool showUI, IntPtr windowHandle, bool modal = false) public ReturnCode Enable(bool showUI, IntPtr windowHandle, bool modal = false)
{ {
var rc = ReturnCode.Failure; var rc = ReturnCode.Failure;
Session.Invoke(() => Session.InternalInvoke(() =>
{ {
var ui = new TW_USERINTERFACE var ui = new TW_USERINTERFACE
{ {

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace NTwain.Internals
{
static class ExceptionExtensions
{
/// <summary>
/// Rethrows the specified excetion while keeping stack trace
/// if not null.
/// </summary>
/// <param name="ex">The ex.</param>
public static void TryRethrow(this Exception ex)
{
if (ex != null)
{
typeof(Exception).GetMethod("PrepForRemoting",
BindingFlags.NonPublic | BindingFlags.Instance)?.Invoke(ex, new object[0]);
throw ex;
}
}
}
}

View File

@@ -68,5 +68,32 @@ namespace NTwain.Resources {
return ResourceManager.GetString("MaxStringLengthExceeded", resourceCulture); return ResourceManager.GetString("MaxStringLengthExceeded", resourceCulture);
} }
} }
/// <summary>
/// Looks up a localized string similar to No data type specified..
/// </summary>
internal static string NoDataTypesSpecified {
get {
return ResourceManager.GetString("NoDataTypesSpecified", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to This platform ({0}) is not supported..
/// </summary>
internal static string PlatformNotSupported {
get {
return ResourceManager.GetString("PlatformNotSupported", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Source is not from this session..
/// </summary>
internal static string SourceNotThisSession {
get {
return ResourceManager.GetString("SourceNotThisSession", resourceCulture);
}
}
} }
} }

View File

@@ -120,4 +120,13 @@
<data name="MaxStringLengthExceeded" xml:space="preserve"> <data name="MaxStringLengthExceeded" xml:space="preserve">
<value>The string value has exceeded the maximum length of {0}.</value> <value>The string value has exceeded the maximum length of {0}.</value>
</data> </data>
<data name="NoDataTypesSpecified" xml:space="preserve">
<value>No data type specified.</value>
</data>
<data name="PlatformNotSupported" xml:space="preserve">
<value>This platform ({0}) is not supported.</value>
</data>
<data name="SourceNotThisSession" xml:space="preserve">
<value>Source is not from this session.</value>
</data>
</root> </root>

View File

@@ -8,6 +8,8 @@ namespace NTwain.Threading
private ManualResetEventSlim waiter; private ManualResetEventSlim waiter;
private Action action; private Action action;
public Exception Error { get; private set; }
public ActionItem(Action action) public ActionItem(Action action)
{ {
this.action = action; this.action = action;
@@ -25,6 +27,10 @@ namespace NTwain.Threading
{ {
action(); action();
} }
catch (Exception ex)
{
Error = ex;
}
finally finally
{ {
waiter?.Set(); waiter?.Set();

View File

@@ -0,0 +1,110 @@
using NTwain.Data.Win32;
using NTwain.Internals;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using System.Threading;
namespace NTwain.Threading
{
/// <summary>
/// Provides an internal message pump on non-Windows using a background thread.
/// </summary>
class DispatcherLoop : IThreadContext
{
readonly TwainSession session;
BlockingCollection<ActionItem> actionQueue;
CancellationTokenSource stopToken;
Thread loopThread;
public DispatcherLoop(TwainSession session)
{
this.session = session;
}
public void Start()
{
if (loopThread != null) return;
Debug.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}: {nameof(WinMsgLoop)}.{nameof(Start)}()");
actionQueue = new BlockingCollection<ActionItem>();
stopToken = new CancellationTokenSource();
// startWaiter ensures thread is running before this method returns
using (var startWaiter = new ManualResetEventSlim())
{
loopThread = new Thread(new ThreadStart(() =>
{
Debug.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}: starting msg pump...");
startWaiter.Set();
try
{
while (actionQueue.TryTake(out ActionItem work, -1, stopToken.Token))
{
work.DoAction();
}
}
catch (OperationCanceledException) { }
finally
{
// clear queue
actionQueue.Dispose();
loopThread = null;
}
}));
loopThread.IsBackground = true;
loopThread.TrySetApartmentState(ApartmentState.STA);
loopThread.Start();
startWaiter.Wait();
}
}
public void Stop()
{
if (!stopToken.IsCancellationRequested) stopToken.Cancel();
}
bool IsSameThread()
{
return loopThread == Thread.CurrentThread || loopThread == null;
}
public void Invoke(Action action)
{
if (IsSameThread())
{
// ok
action();
}
else
{
// queue up work
using (var waiter = new ManualResetEventSlim())
{
var work = new ActionItem(waiter, action);
actionQueue.TryAdd(work);
waiter.Wait();
work.Error.TryRethrow();
}
}
}
public void BeginInvoke(Action action)
{
actionQueue.TryAdd(new ActionItem(action));
}
}
}

View File

@@ -0,0 +1,32 @@
using System;
namespace NTwain.Threading
{
/// <summary>
/// Interface for running some work on an associated thread.
/// </summary>
interface IThreadContext
{
/// <summary>
/// Starts the context if supported.
/// </summary>
void Start();
/// <summary>
/// Stops the context if supported.
/// </summary>
void Stop();
/// <summary>
/// Runs the action synchronously on the associated thread.
/// </summary>
/// <param name="action"></param>
void Invoke(Action action);
/// <summary>
/// Runs the action asynchronously on the associated thread.
/// </summary>
/// <param name="action"></param>
void BeginInvoke(Action action);
}
}

View File

@@ -0,0 +1,83 @@
using NTwain.Internals;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace NTwain.Threading
{
/// <summary>
/// A <see cref="IThreadContext"/> using <see cref="SynchronizationContext"/>
/// for running actions on an UI thread.
/// </summary>
class UIThreadContext : IThreadContext
{
private readonly SynchronizationContext context;
/// <summary>
/// Creates a new <see cref="UIThreadContext"/> using
/// the specified <see cref="SynchronizationContext"/>.
/// </summary>
/// <param name="context"></param>
public UIThreadContext(SynchronizationContext context)
{
this.context = context;
}
/// <summary>
/// Runs the action asynchronously on the <see cref="SynchronizationContext"/> thread.
/// </summary>
/// <param name="action"></param>
public void BeginInvoke(Action action)
{
if (action == null) return;
context.Post(o =>
{
try
{
action();
}
catch (Exception ex)
{
// TODO: do something
}
}, null);
}
/// <summary>
/// Runs the action synchronously on the <see cref="SynchronizationContext"/> thread.
/// </summary>
/// <param name="action"></param>
public void Invoke(Action action)
{
if (action == null) return;
Exception error = null;
context.Send(o =>
{
try
{
action();
}
catch (Exception ex)
{
error = ex;
}
}, null);
error.TryRethrow();
}
void IThreadContext.Start()
{
}
void IThreadContext.Stop()
{
}
}
}

View File

@@ -1,10 +1,12 @@
using NTwain.Data.Win32; using NTwain.Data.Win32;
using NTwain.Internals;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security; using System.Security;
using System.Text; using System.Text;
@@ -15,15 +17,13 @@ namespace NTwain.Threading
/// <summary> /// <summary>
/// Provides an internal message pump on Windows using a background thread. /// Provides an internal message pump on Windows using a background thread.
/// </summary> /// </summary>
class WinMsgLoop class WinMsgLoop : IThreadContext
{ {
static ushort classAtom; static ushort classAtom;
static IntPtr hInstance; static IntPtr hInstance;
static readonly int dequeMsg = UnsafeNativeMethods.RegisterWindowMessage("WinMsgLoopQueue"); static readonly uint dequeMsg = UnsafeNativeMethods.RegisterWindowMessageW("WinMsgLoopQueue");
// keep delegate around to prevent GC
const int CW_USEDEFAULT = -1; static readonly WndProcDelegate WndProc = new WndProcDelegate((hWnd, msg, wParam, lParam) =>
static IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{ {
Debug.WriteLine($"Dummy window got msg {(WindowMessage)msg}."); Debug.WriteLine($"Dummy window got msg {(WindowMessage)msg}.");
switch ((WindowMessage)msg) switch ((WindowMessage)msg)
@@ -32,20 +32,22 @@ namespace NTwain.Threading
UnsafeNativeMethods.PostQuitMessage(0); UnsafeNativeMethods.PostQuitMessage(0);
return IntPtr.Zero; return IntPtr.Zero;
} }
return UnsafeNativeMethods.DefWindowProc(hWnd, msg, wParam, lParam); return UnsafeNativeMethods.DefWindowProcW(hWnd, msg, wParam, lParam);
} });
const int CW_USEDEFAULT = -1;
static void InitGlobal() static void InitGlobal()
{ {
if (classAtom == 0) if (classAtom == 0)
{ {
hInstance = UnsafeNativeMethods.GetModuleHandle(null); hInstance = UnsafeNativeMethods.GetModuleHandleW(null);
var wc = new WNDCLASSEX(); var wc = new WNDCLASSEX();
wc.cbSize = Marshal.SizeOf(wc); wc.cbSize = Marshal.SizeOf(wc);
wc.style = ClassStyles.CS_VREDRAW | ClassStyles.CS_HREDRAW; wc.style = ClassStyles.CS_VREDRAW | ClassStyles.CS_HREDRAW;
var procPtr = Marshal.GetFunctionPointerForDelegate(new WndProcDelegate(WndProc)); var procPtr = Marshal.GetFunctionPointerForDelegate(WndProc);
wc.lpfnWndProc = procPtr; wc.lpfnWndProc = procPtr;
wc.cbClsExtra = 0; wc.cbClsExtra = 0;
@@ -58,7 +60,7 @@ namespace NTwain.Threading
wc.lpszClassName = Guid.NewGuid().ToString("n"); wc.lpszClassName = Guid.NewGuid().ToString("n");
wc.hIconSm = IntPtr.Zero; wc.hIconSm = IntPtr.Zero;
classAtom = UnsafeNativeMethods.RegisterClassEx(ref wc); classAtom = UnsafeNativeMethods.RegisterClassExW(ref wc);
if (classAtom == 0) if (classAtom == 0)
{ {
throw new Win32Exception(); throw new Win32Exception();
@@ -99,7 +101,7 @@ namespace NTwain.Threading
{ {
Debug.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}: starting msg pump..."); Debug.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}: starting msg pump...");
hWnd = UnsafeNativeMethods.CreateWindowEx(WindowStylesEx.WS_EX_LEFT, classAtom, Guid.NewGuid().ToString(), hWnd = UnsafeNativeMethods.CreateWindowExW(WindowStylesEx.WS_EX_LEFT, classAtom, Guid.NewGuid().ToString(),
WindowStyles.WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, WindowStyles.WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
IntPtr.Zero, IntPtr.Zero, hInstance, IntPtr.Zero); IntPtr.Zero, IntPtr.Zero, hInstance, IntPtr.Zero);
if (hWnd == IntPtr.Zero) if (hWnd == IntPtr.Zero)
@@ -119,7 +121,11 @@ namespace NTwain.Threading
startWaiter.Set(); startWaiter.Set();
MSG msg = default; MSG msg = default;
while (!stop && UnsafeNativeMethods.GetMessage(ref msg, IntPtr.Zero, 0, 0)) var msgRes = 0;
try
{
while (!stop &&
(msgRes = UnsafeNativeMethods.GetMessageW(ref msg, IntPtr.Zero, 0, 0)) > 0)
{ {
UnsafeNativeMethods.TranslateMessage(ref msg); UnsafeNativeMethods.TranslateMessage(ref msg);
if (!session.HandleWindowsMessage(ref msg)) if (!session.HandleWindowsMessage(ref msg))
@@ -134,10 +140,13 @@ namespace NTwain.Threading
} }
else else
{ {
UnsafeNativeMethods.DispatchMessage(ref msg); UnsafeNativeMethods.DispatchMessageW(ref msg);
} }
} }
} }
}
finally
{
// clear queue // clear queue
while (actionQueue.TryDequeue(out ActionItem dummy)) { } while (actionQueue.TryDequeue(out ActionItem dummy)) { }
@@ -145,13 +154,14 @@ namespace NTwain.Threading
hWnd = IntPtr.Zero; hWnd = IntPtr.Zero;
stop = false; stop = false;
} }
}
})); }));
loopThread.IsBackground = true; loopThread.IsBackground = true;
loopThread.SetApartmentState(ApartmentState.STA); loopThread.SetApartmentState(ApartmentState.STA);
loopThread.Start(); loopThread.Start();
startWaiter.Wait(); startWaiter.Wait();
if (startErr != null) throw startErr; startErr.TryRethrow();
} }
} }
@@ -181,10 +191,7 @@ namespace NTwain.Threading
return loopThread == Thread.CurrentThread || loopThread == null; return loopThread == Thread.CurrentThread || loopThread == null;
} }
/// <summary>
/// Runs the action synchronously on the internal message pump thread.
/// </summary>
/// <param name="action"></param>
public void Invoke(Action action) public void Invoke(Action action)
{ {
if (IsSameThread()) if (IsSameThread())
@@ -197,24 +204,23 @@ namespace NTwain.Threading
// queue up work // queue up work
using (var waiter = new ManualResetEventSlim()) using (var waiter = new ManualResetEventSlim())
{ {
actionQueue.Enqueue(new ActionItem(waiter, action)); var work = new ActionItem(waiter, action);
UnsafeNativeMethods.PostMessage(hWnd, dequeMsg, IntPtr.Zero, IntPtr.Zero); actionQueue.Enqueue(work);
UnsafeNativeMethods.PostMessageW(hWnd, dequeMsg, IntPtr.Zero, IntPtr.Zero);
waiter.Wait(); waiter.Wait();
work.Error.TryRethrow();
} }
} }
} }
/// <summary>
/// Runs the action asynchronously on the internal message pump thread.
/// </summary>
/// <param name="action"></param>
public void BeginInvoke(Action action) public void BeginInvoke(Action action)
{ {
if (hWnd == IntPtr.Zero) action(); if (hWnd == IntPtr.Zero) action();
else else
{ {
actionQueue.Enqueue(new ActionItem(action)); actionQueue.Enqueue(new ActionItem(action));
UnsafeNativeMethods.PostMessage(hWnd, dequeMsg, IntPtr.Zero, IntPtr.Zero); UnsafeNativeMethods.PostMessageW(hWnd, dequeMsg, IntPtr.Zero, IntPtr.Zero);
} }
} }
@@ -222,43 +228,48 @@ namespace NTwain.Threading
[SuppressUnmanagedCodeSecurity] [SuppressUnmanagedCodeSecurity]
internal static class UnsafeNativeMethods internal static class UnsafeNativeMethods
{ {
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, ThrowOnUnmappableChar = true, BestFitMapping = false)] [DllImport("kernel32.dll", CharSet = CharSet.Unicode,
public static extern IntPtr GetModuleHandle(string modName); SetLastError = true, ExactSpelling = true)]
public static extern IntPtr GetModuleHandleW([MarshalAs(UnmanagedType.LPWStr)] string modName);
[DllImport("user32.dll", CharSet = CharSet.Auto)] [DllImport("user32.dll", CharSet = CharSet.Unicode,
[return: MarshalAs(UnmanagedType.Bool)] SetLastError = true, ExactSpelling = true)]
public static extern bool GetMessage([In, Out] ref MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax); public static extern int GetMessageW([In, Out] ref MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
[DllImport("user32.dll", CharSet = CharSet.Auto)] [DllImport("user32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern bool TranslateMessage([In, Out] ref MSG lpMsg); public static extern int TranslateMessage([In, Out] ref MSG lpMsg);
[DllImport("user32.dll", CharSet = CharSet.Auto)] [DllImport("user32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern IntPtr DispatchMessage([In] ref MSG lpmsg); public static extern IntPtr DispatchMessageW([In] ref MSG lpmsg);
[DllImport("user32.dll", CharSet = CharSet.Auto)] [DllImport("user32.dll", CharSet = CharSet.Unicode,
public static extern int PostMessage(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam); SetLastError = true, ExactSpelling = true)]
public static extern int PostMessageW(IntPtr hWnd, uint msg, IntPtr wparam, IntPtr lparam);
[DllImport("user32.dll")] [DllImport("user32.dll", ExactSpelling = true)]
public static extern void PostQuitMessage(int nExitCode); public static extern void PostQuitMessage(int nExitCode);
[DllImport("user32.dll", CharSet = CharSet.Auto)] [DllImport("user32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern IntPtr DefWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); public static extern IntPtr DefWindowProcW(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, BestFitMapping = false)] [DllImport("user32.dll", CharSet = CharSet.Unicode,
public static extern int RegisterWindowMessage(string msg); SetLastError = true, ExactSpelling = true)]
public static extern uint RegisterWindowMessageW([MarshalAs(UnmanagedType.LPWStr)] string msg);
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", CharSet = CharSet.Unicode,
public static extern ushort RegisterClassEx([In] ref WNDCLASSEX lpwcx); SetLastError = true, ExactSpelling = true)]
public static extern ushort RegisterClassExW([In] ref WNDCLASSEX lpwcx);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)] [DllImport("user32.dll", CharSet = CharSet.Unicode,
public static extern IntPtr CreateWindowEx(WindowStylesEx dwExStyle, SetLastError = true, ExactSpelling = true)]
public static extern IntPtr CreateWindowExW(WindowStylesEx dwExStyle,
ushort lpszClassName, ushort lpszClassName,
[MarshalAs(UnmanagedType.LPStr)] string lpszWindowName, [MarshalAs(UnmanagedType.LPTStr)] string lpszWindowName,
WindowStyles style, WindowStyles style,
int x, int y, int width, int height, int x, int y, int width, int height,
IntPtr hWndParent, IntPtr hMenu, IntPtr hInst, IntPtr lpParam); IntPtr hWndParent, IntPtr hMenu, IntPtr hInst, IntPtr lpParam);
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true, ExactSpelling = true)]
public static extern bool DestroyWindow(IntPtr hWnd); public static extern bool DestroyWindow(IntPtr hWnd);
} }
} }

View File

@@ -1,5 +1,6 @@
using NTwain.Data; using NTwain.Data;
using NTwain.Internals; using NTwain.Internals;
using NTwain.Resources;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Reflection; using System.Reflection;
@@ -57,7 +58,7 @@ namespace NTwain
if (image) dg |= DataGroups.Image; if (image) dg |= DataGroups.Image;
if (audio) dg |= DataGroups.Audio; if (audio) dg |= DataGroups.Audio;
if (dg == DataGroups.None) throw new InvalidOperationException("No data type specified."); if (dg == DataGroups.None) throw new InvalidOperationException(MsgText.NoDataTypesSpecified);
_dg = dg; _dg = dg;
return this; return this;
} }
@@ -138,7 +139,7 @@ namespace NTwain
case PlatformID.Unix: case PlatformID.Unix:
case PlatformID.MacOSX: case PlatformID.MacOSX:
default: default:
throw new PlatformNotSupportedException($"This platform {_platform} is not supported."); throw new PlatformNotSupportedException(string.Format(MsgText.PlatformNotSupported, _platform));
} }
return config; return config;
} }

View File

@@ -1,52 +0,0 @@
//using NTwain.Data;
//using NTwain.Triplets;
//using System;
//using System.Collections.Generic;
//using System.ComponentModel;
//using System.Linq;
//using System.Text;
//namespace NTwain
//{
// partial class TwainSession : IDisposable
// {
// private bool disposedValue = false; // To detect redundant calls
// /// <summary>
// /// Handles actual disposal logic.
// /// </summary>
// /// <param name="disposing"></param>
// protected virtual void Dispose(bool disposing)
// {
// if (!disposedValue)
// {
// if (disposing)
// {
// StepDown(TwainState.DsmLoaded);
// // TODO: dispose managed state (managed objects).
// }
// // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// // TODO: set large fields to null.
// disposedValue = true;
// }
// }
// // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
// // ~TwainSession() {
// // // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
// // Dispose(false);
// // }
// /// <summary>
// /// Closes any open TWAIN objects.
// /// </summary>
// public void Dispose()
// {
// Dispose(true);
// // TODO: uncomment the following line if the finalizer is overridden above.
// // GC.SuppressFinalize(this);
// }
// }
//}

View File

@@ -22,8 +22,10 @@ namespace NTwain
ReturnCode Handle32BitCallback(TW_IDENTITY origin, TW_IDENTITY destination, ReturnCode Handle32BitCallback(TW_IDENTITY origin, TW_IDENTITY destination,
DataGroups dg, DataArgumentType dat, Message msg, IntPtr data) DataGroups dg, DataArgumentType dat, Message msg, IntPtr data)
{ {
// from the docs this needs to return Success
// before handling the msg thus BeginInvoke is used.
Debug.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}: {nameof(Handle32BitCallback)}({dg}, {dat}, {msg}, {data})"); Debug.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}: {nameof(Handle32BitCallback)}({dg}, {dat}, {msg}, {data})");
BeginInvoke(() => InternalBeginInvoke(() =>
{ {
Debug.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}: in BeginInvoke {nameof(Handle32BitCallback)}({dg}, {dat}, {msg}, {data})"); Debug.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}: in BeginInvoke {nameof(Handle32BitCallback)}({dg}, {dat}, {msg}, {data})");
HandleSourceMsg(msg); HandleSourceMsg(msg);

View File

@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading;
namespace NTwain namespace NTwain
{ {
@@ -52,9 +53,12 @@ namespace NTwain
/// </summary> /// </summary>
/// <param name="e"></param> /// <param name="e"></param>
internal protected virtual void OnSourceDisabled(SourceDisabledEventArgs e) internal protected virtual void OnSourceDisabled(SourceDisabledEventArgs e)
{
ExternalInvoke(() =>
{ {
var handler = SourceDisabled; var handler = SourceDisabled;
handler?.Invoke(this, e); handler?.Invoke(this, e);
});
} }
/// <summary> /// <summary>
@@ -67,9 +71,12 @@ namespace NTwain
/// </summary> /// </summary>
/// <param name="e"></param> /// <param name="e"></param>
protected virtual void OnDeviceEventReceived(DeviceEventArgs e) protected virtual void OnDeviceEventReceived(DeviceEventArgs e)
{
ExternalInvoke(() =>
{ {
var handler = DeviceEventReceived; var handler = DeviceEventReceived;
handler?.Invoke(this, e); handler?.Invoke(this, e);
});
} }
/// <summary> /// <summary>
@@ -82,9 +89,12 @@ namespace NTwain
/// </summary> /// </summary>
/// <param name="e"></param> /// <param name="e"></param>
protected virtual void OnTransferReady(TransferReadyEventArgs e) protected virtual void OnTransferReady(TransferReadyEventArgs e)
{
ExternalInvoke(() =>
{ {
var handler = TransferReady; var handler = TransferReady;
handler?.Invoke(this, e); handler?.Invoke(this, e);
});
} }
@@ -92,6 +102,21 @@ namespace NTwain
///// Occurs when data has been transferred. ///// Occurs when data has been transferred.
///// </summary> ///// </summary>
//public event EventHandler<DataTransferredEventArgs> DataTransferred; //public event EventHandler<DataTransferredEventArgs> DataTransferred;
///// <summary>
///// Raises the <see cref="DataTransferred"/> event.
///// </summary>
///// <param name="e"></param>
//protected virtual void OnDataTransferred(DataTransferredEventArgs e)
//{
// ExternalInvoke(() =>
// {
// var handler = DataTransferred;
// handler?.Invoke(this, e);
// });
//}
/// <summary> /// <summary>
/// Occurs when an error has been encountered during transfer. /// Occurs when an error has been encountered during transfer.
/// </summary> /// </summary>
@@ -102,9 +127,12 @@ namespace NTwain
/// </summary> /// </summary>
/// <param name="e"></param> /// <param name="e"></param>
protected virtual void OnTransferError(TransferErrorEventArgs e) protected virtual void OnTransferError(TransferErrorEventArgs e)
{
ExternalInvoke(() =>
{ {
var handler = TransferError; var handler = TransferError;
handler?.Invoke(this, e); handler?.Invoke(this, e);
});
} }
@@ -119,9 +147,12 @@ namespace NTwain
/// </summary> /// </summary>
/// <param name="propertyName">Name of the property.</param> /// <param name="propertyName">Name of the property.</param>
protected void RaisePropertyChanged(string propertyName) protected void RaisePropertyChanged(string propertyName)
{
ExternalInvoke(() =>
{ {
var handler = PropertyChanged; var handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName)); handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
});
} }
} }
} }

View File

@@ -1,4 +1,5 @@
using NTwain.Data; using NTwain.Data;
using NTwain.Resources;
using NTwain.Threading; using NTwain.Threading;
using NTwain.Triplets; using NTwain.Triplets;
using System; using System;
@@ -19,43 +20,89 @@ namespace NTwain
{ {
internal readonly TwainConfig Config; internal readonly TwainConfig Config;
private IntPtr _hWnd;
// cache generated twain sources so if you get same source from same session it'll return the same object // cache generated twain sources so if you get same source from same session it'll return the same object
readonly Dictionary<string, DataSource> _ownedSources = new Dictionary<string, DataSource>(); readonly Dictionary<string, DataSource> _ownedSources = new Dictionary<string, DataSource>();
// need to keep delegate around to prevent GC? // need to keep delegate around to prevent GC
readonly Callback32 _callback32Delegate; readonly Callback32 _callback32Delegate;
// for windows only
readonly IThreadContext _internalContext;
readonly WinMsgLoop _winMsgLoop; private IntPtr _hWnd;
private IThreadContext _externalContext;
/// <summary> /// <summary>
/// Constructs a new <see cref="TwainSession"/>. /// Constructs a new <see cref="TwainSession"/>.
/// </summary> /// </summary>
/// <param name="config"></param> /// <param name="config"></param>
/// <exception cref="ArgumentNullException"></exception>
public TwainSession(TwainConfig config) public TwainSession(TwainConfig config)
{ {
Config = config; Config = config ?? throw new ArgumentNullException(nameof(config));
SetSynchronizationContext(SynchronizationContext.Current);
switch (config.Platform) switch (config.Platform)
{ {
case PlatformID.MacOSX: case PlatformID.MacOSX:
case PlatformID.Unix: case PlatformID.Unix:
_internalContext = new DispatcherLoop(this);
break;
default: default:
_winMsgLoop = new WinMsgLoop(this); _internalContext = new WinMsgLoop(this);
_callback32Delegate = new Callback32(Handle32BitCallback); _callback32Delegate = new Callback32(Handle32BitCallback);
break; break;
} }
} }
/// <summary>
public void Invoke(Action action) /// Sets the optional synchronization context.
/// Because most TWAIN-related things are happening on a different thread,
/// this allows events to be raised on the thread associated with this context and
/// may be useful if you want to handle them in the UI thread.
/// </summary>
/// <param name="context">Usually you want to use <see cref="SynchronizationContext.Current"/> while on the UI thread.</param>
public void SetSynchronizationContext(SynchronizationContext context)
{ {
if (_winMsgLoop != null) _winMsgLoop.Invoke(action); if (context == null) _externalContext = null;
else _externalContext = new UIThreadContext(context);
}
/// <summary>
/// Synchronously invokes an action on the external user thread if possible.
/// </summary>
/// <param name="action"></param>
void ExternalInvoke(Action action)
{
if (_externalContext != null) _externalContext.Invoke(action);
action();
}
/// <summary>
/// Asynchronously invokes an action on the external user thread if possible.
/// </summary>
/// <param name="action"></param>
void ExternalBeginInvoke(Action action)
{
if (_externalContext != null) _externalContext.BeginInvoke(action);
action();
}
/// <summary>
/// Synchronously invokes an action on the internal TWAIN thread if possible.
/// </summary>
/// <param name="action"></param>
internal void InternalInvoke(Action action)
{
if (_internalContext != null) _internalContext.Invoke(action);
else action(); else action();
} }
public void BeginInvoke(Action action) /// <summary>
/// Asynchronously invokes an action on the internal TWAIN thread if possible.
/// </summary>
/// <param name="action"></param>
void InternalBeginInvoke(Action action)
{ {
if (_winMsgLoop != null) _winMsgLoop.BeginInvoke(action); if (_internalContext != null) _internalContext.BeginInvoke(action);
else action(); else action();
} }
@@ -70,7 +117,7 @@ namespace NTwain
var rc = DGControl.Parent.OpenDSM(hWnd); var rc = DGControl.Parent.OpenDSM(hWnd);
if (rc == ReturnCode.Success) if (rc == ReturnCode.Success)
{ {
_winMsgLoop?.Start(); _internalContext?.Start();
} }
return rc; return rc;
} }
@@ -84,7 +131,7 @@ namespace NTwain
var rc = DGControl.Parent.CloseDSM(_hWnd); var rc = DGControl.Parent.CloseDSM(_hWnd);
if (rc == ReturnCode.Success) if (rc == ReturnCode.Success)
{ {
_winMsgLoop?.Stop(); _internalContext?.Stop();
} }
return rc; return rc;
} }
@@ -116,6 +163,14 @@ namespace NTwain
rc = DGControl.Identity.CloseDS(CurrentSource.Identity32); rc = DGControl.Identity.CloseDS(CurrentSource.Identity32);
if (rc != ReturnCode.Success) return rc; if (rc != ReturnCode.Success) return rc;
break; break;
case TwainState.SourceEnabled:
rc = DGControl.UserInterface.DisableDS(ref _lastEnableUI, false);
if (rc != ReturnCode.Success) return rc;
break;
case TwainState.TransferReady:
case TwainState.Transferring:
_disableDSNow = true;
break;
} }
} }
return rc; return rc;
@@ -210,7 +265,7 @@ namespace NTwain
{ {
if (value.Session != this) if (value.Session != this)
{ {
throw new InvalidOperationException("Source is not from this session."); throw new InvalidOperationException(MsgText.SourceNotThisSession);
} }
var rc = DGControl.Identity.Set(value); var rc = DGControl.Identity.Set(value);
RaisePropertyChanged(nameof(DefaultSource)); RaisePropertyChanged(nameof(DefaultSource));
@@ -274,7 +329,7 @@ namespace NTwain
/// </returns> /// </returns>
public override string ToString() public override string ToString()
{ {
return $"Session: {State}"; return State.ToString();
} }
} }
} }