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.
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:
* Targets latest TWAIN version (2.4 as of this writing)
* Supports all the TWAIN functions in the spec
* Works with both 32 and 64 bit data sources
* Works on non-Windows platforms using dotnet core or mono (maybe)
* Supports all the TWAIN functions in the spec (directly or through dotnet wrapper)
* Works with both 32 or 64 bit data sources, from 32 or 64 bit apps.
* Works on non-Windows platforms using dotnet core
## Using the lib

View File

@@ -63,16 +63,16 @@ namespace ConsoleApp
Console.WriteLine("ERROR: " + ex.ToString());
}
Console.WriteLine("------------------------------------------------");
Console.WriteLine("Test in progress, press Enter to stop testing...");
Console.WriteLine("------------------------------------------------");
Console.WriteLine("---------------------------------------------");
Console.WriteLine("Test in progress, press Enter to stop testing");
Console.WriteLine("---------------------------------------------");
Console.ReadLine();
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.WriteLine("----------------------");
Console.WriteLine("Press Enter to exit...");
Console.WriteLine("----------------------");
}
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
#if _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#include <iostream>
#include "twain.h"
int main()
{
bool is32 = sizeof(void*) == 4;
bool is64 = sizeof(void*) == 8;
std::cout << (is32 ? "32bit= " : "") << (is64 ? "64bit" : "") << std::endl;
bool is32 = sizeof(void *) == 4;
bool is64 = sizeof(void *) == 8;
std::cout << (is32 ? "32bit" : "") << (is64 ? "64bit" : "") << std::endl;
std::cout << std::endl;
std::cout << "TW_STR32 = " << sizeof TW_STR32 << std::endl;
std::cout << "TW_STR64 = " << sizeof TW_STR64 << std::endl;
std::cout << "TW_STR128 = " << sizeof TW_STR128 << std::endl;
std::cout << "TW_STR255 = " << sizeof TW_STR255 << std::endl;
std::cout << "TW_STR32 = " << sizeof(TW_STR32) << std::endl;
std::cout << "TW_STR64 = " << sizeof(TW_STR64) << std::endl;
std::cout << "TW_STR128 = " << sizeof(TW_STR128) << std::endl;
std::cout << "TW_STR255 = " << sizeof(TW_STR255) << std::endl;
std::cout << std::endl;
std::cout << "TW_INT8 = " << sizeof TW_INT8 << std::endl;
std::cout << "TW_INT16 = " << sizeof TW_INT16 << std::endl;
std::cout << "TW_INT32 = " << sizeof TW_INT32 << std::endl;
std::cout << "TW_UINT8 = " << sizeof TW_UINT8 << std::endl;
std::cout << "TW_UINT16 = " << sizeof TW_UINT16 << std::endl;
std::cout << "TW_UINT32 = " << sizeof TW_UINT32 << std::endl;
std::cout << "TW_BOOL = " << sizeof TW_BOOL << std::endl;
std::cout << "TW_INT8 = " << sizeof(TW_INT8) << std::endl;
std::cout << "TW_INT16 = " << sizeof(TW_INT16) << std::endl;
std::cout << "TW_INT32 = " << sizeof(TW_INT32) << std::endl;
std::cout << "TW_UINT8 = " << sizeof(TW_UINT8) << std::endl;
std::cout << "TW_UINT16 = " << sizeof(TW_UINT16) << std::endl;
std::cout << "TW_UINT32 = " << sizeof(TW_UINT32) << std::endl;
std::cout << "TW_BOOL = " << sizeof(TW_BOOL) << std::endl;
std::cout << 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 << "TW_IDENTITY = " << sizeof(TW_IDENTITY) << 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;
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
{
// TODO: _itemType in mac is TW_UINT32?
TW_UINT16 _itemType;
TW_UINT32 _numItems;
object[] _itemList;
public TW_UINT16 ItemType;
public TW_UINT32 NumItems;
public IntPtr ItemList;
}
[StructLayout(LayoutKind.Sequential, Pack = 2),
@@ -181,12 +181,12 @@ namespace NTwain.Data
partial class TW_ENUMERATION
{
// TODO: _itemType in mac is TW_UINT32?
TW_UINT16 _itemType;
public TW_UINT16 ItemType;
// TODO: these are UInt64 in linux 64 bit?
TW_UINT32 _numItems;
TW_UINT32 _currentIndex;
TW_UINT32 _defaultIndex;
object[] _itemList;
public TW_UINT32 NumItems;
public TW_UINT32 CurrentIndex;
public TW_UINT32 DefaultIndex;
public IntPtr ItemList;
}
[StructLayout(LayoutKind.Sequential, Pack = 2)]
@@ -431,8 +431,8 @@ namespace NTwain.Data
partial class TW_ONEVALUE
{
// TODO: mac is different?
TW_UINT16 _itemType;
TW_UINT32 _item;
public TW_UINT16 ItemType;
public TW_UINT32 Item;
}
[StructLayout(LayoutKind.Sequential, Pack = 2)]
@@ -468,12 +468,12 @@ namespace NTwain.Data
partial class TW_RANGE
{
// TODO: mac & linux are different?
TW_UINT16 _itemType;
TW_UINT32 _minValue;
TW_UINT32 _maxValue;
TW_UINT32 _stepSize;
TW_UINT32 _defaultValue;
TW_UINT32 _currentValue;
public TW_UINT16 ItemType;
public TW_UINT32 MinValue;
public TW_UINT32 MaxValue;
public TW_UINT32 StepSize;
public TW_UINT32 DefaultValue;
public TW_UINT32 CurrentValue;
}
//[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>
/// Used to get audio info.
/// </summary>
@@ -1120,52 +1087,6 @@ namespace NTwain.Data
#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>
// /// This structure is used to pass specific information between the data source and the application
// /// through <see cref="TW_EXTIMAGEINFO"/>.
@@ -1954,21 +1875,6 @@ namespace NTwain.Data
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>
/// 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>
// ///// 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.

View File

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

View File

@@ -10,7 +10,7 @@ namespace NTwain.Data.Win32
/// <summary>
/// Enumerated values of window messages.
/// </summary>
enum WindowMessage
enum WindowMessage : uint
{
/// <summary>
/// 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)
{
var rc = ReturnCode.Failure;
Session.Invoke(() =>
Session.InternalInvoke(() =>
{
var ui = new TW_USERINTERFACE
{
@@ -72,7 +72,7 @@ namespace NTwain
public ReturnCode Enable(bool showUI, IntPtr windowHandle, bool modal = false)
{
var rc = ReturnCode.Failure;
Session.Invoke(() =>
Session.InternalInvoke(() =>
{
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);
}
}
/// <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">
<value>The string value has exceeded the maximum length of {0}.</value>
</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>

View File

@@ -8,6 +8,8 @@ namespace NTwain.Threading
private ManualResetEventSlim waiter;
private Action action;
public Exception Error { get; private set; }
public ActionItem(Action action)
{
this.action = action;
@@ -25,6 +27,10 @@ namespace NTwain.Threading
{
action();
}
catch (Exception ex)
{
Error = ex;
}
finally
{
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.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;
@@ -15,15 +17,13 @@ namespace NTwain.Threading
/// <summary>
/// Provides an internal message pump on Windows using a background thread.
/// </summary>
class WinMsgLoop
class WinMsgLoop : IThreadContext
{
static ushort classAtom;
static IntPtr hInstance;
static readonly int dequeMsg = UnsafeNativeMethods.RegisterWindowMessage("WinMsgLoopQueue");
const int CW_USEDEFAULT = -1;
static IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
static readonly uint dequeMsg = UnsafeNativeMethods.RegisterWindowMessageW("WinMsgLoopQueue");
// keep delegate around to prevent GC
static readonly WndProcDelegate WndProc = new WndProcDelegate((hWnd, msg, wParam, lParam) =>
{
Debug.WriteLine($"Dummy window got msg {(WindowMessage)msg}.");
switch ((WindowMessage)msg)
@@ -32,20 +32,22 @@ namespace NTwain.Threading
UnsafeNativeMethods.PostQuitMessage(0);
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()
{
if (classAtom == 0)
{
hInstance = UnsafeNativeMethods.GetModuleHandle(null);
hInstance = UnsafeNativeMethods.GetModuleHandleW(null);
var wc = new WNDCLASSEX();
wc.cbSize = Marshal.SizeOf(wc);
wc.style = ClassStyles.CS_VREDRAW | ClassStyles.CS_HREDRAW;
var procPtr = Marshal.GetFunctionPointerForDelegate(new WndProcDelegate(WndProc));
var procPtr = Marshal.GetFunctionPointerForDelegate(WndProc);
wc.lpfnWndProc = procPtr;
wc.cbClsExtra = 0;
@@ -58,7 +60,7 @@ namespace NTwain.Threading
wc.lpszClassName = Guid.NewGuid().ToString("n");
wc.hIconSm = IntPtr.Zero;
classAtom = UnsafeNativeMethods.RegisterClassEx(ref wc);
classAtom = UnsafeNativeMethods.RegisterClassExW(ref wc);
if (classAtom == 0)
{
throw new Win32Exception();
@@ -99,7 +101,7 @@ namespace NTwain.Threading
{
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,
IntPtr.Zero, IntPtr.Zero, hInstance, IntPtr.Zero);
if (hWnd == IntPtr.Zero)
@@ -119,31 +121,39 @@ namespace NTwain.Threading
startWaiter.Set();
MSG msg = default;
while (!stop && UnsafeNativeMethods.GetMessage(ref msg, IntPtr.Zero, 0, 0))
var msgRes = 0;
try
{
UnsafeNativeMethods.TranslateMessage(ref msg);
if (!session.HandleWindowsMessage(ref msg))
while (!stop &&
(msgRes = UnsafeNativeMethods.GetMessageW(ref msg, IntPtr.Zero, 0, 0)) > 0)
{
if (msg.message == dequeMsg)
UnsafeNativeMethods.TranslateMessage(ref msg);
if (!session.HandleWindowsMessage(ref msg))
{
if (actionQueue.TryDequeue(out ActionItem work))
if (msg.message == dequeMsg)
{
work.DoAction();
if (stop) break;
if (actionQueue.TryDequeue(out ActionItem work))
{
work.DoAction();
if (stop) break;
}
}
else
{
UnsafeNativeMethods.DispatchMessageW(ref msg);
}
}
else
{
UnsafeNativeMethods.DispatchMessage(ref msg);
}
}
}
// clear queue
while (actionQueue.TryDequeue(out ActionItem dummy)) { }
finally
{
// clear queue
while (actionQueue.TryDequeue(out ActionItem dummy)) { }
loopThread = null;
hWnd = IntPtr.Zero;
stop = false;
loopThread = null;
hWnd = IntPtr.Zero;
stop = false;
}
}
}));
loopThread.IsBackground = true;
@@ -151,7 +161,7 @@ namespace NTwain.Threading
loopThread.Start();
startWaiter.Wait();
if (startErr != null) throw startErr;
startErr.TryRethrow();
}
}
@@ -181,10 +191,7 @@ namespace NTwain.Threading
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)
{
if (IsSameThread())
@@ -197,24 +204,23 @@ namespace NTwain.Threading
// queue up work
using (var waiter = new ManualResetEventSlim())
{
actionQueue.Enqueue(new ActionItem(waiter, action));
UnsafeNativeMethods.PostMessage(hWnd, dequeMsg, IntPtr.Zero, IntPtr.Zero);
var work = new ActionItem(waiter, action);
actionQueue.Enqueue(work);
UnsafeNativeMethods.PostMessageW(hWnd, dequeMsg, IntPtr.Zero, IntPtr.Zero);
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)
{
if (hWnd == IntPtr.Zero) action();
else
{
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]
internal static class UnsafeNativeMethods
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, ThrowOnUnmappableChar = true, BestFitMapping = false)]
public static extern IntPtr GetModuleHandle(string modName);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode,
SetLastError = true, ExactSpelling = true)]
public static extern IntPtr GetModuleHandleW([MarshalAs(UnmanagedType.LPWStr)] string modName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetMessage([In, Out] ref MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
[DllImport("user32.dll", CharSet = CharSet.Unicode,
SetLastError = true, ExactSpelling = true)]
public static extern int GetMessageW([In, Out] ref MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern bool TranslateMessage([In, Out] ref MSG lpMsg);
[DllImport("user32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern int TranslateMessage([In, Out] ref MSG lpMsg);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr DispatchMessage([In] ref MSG lpmsg);
[DllImport("user32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern IntPtr DispatchMessageW([In] ref MSG lpmsg);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int PostMessage(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam);
[DllImport("user32.dll", CharSet = CharSet.Unicode,
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);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr DefWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern IntPtr DefWindowProcW(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, BestFitMapping = false)]
public static extern int RegisterWindowMessage(string msg);
[DllImport("user32.dll", CharSet = CharSet.Unicode,
SetLastError = true, ExactSpelling = true)]
public static extern uint RegisterWindowMessageW([MarshalAs(UnmanagedType.LPWStr)] string msg);
[DllImport("user32.dll", SetLastError = true)]
public static extern ushort RegisterClassEx([In] ref WNDCLASSEX lpwcx);
[DllImport("user32.dll", CharSet = CharSet.Unicode,
SetLastError = true, ExactSpelling = true)]
public static extern ushort RegisterClassExW([In] ref WNDCLASSEX lpwcx);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)]
public static extern IntPtr CreateWindowEx(WindowStylesEx dwExStyle,
[DllImport("user32.dll", CharSet = CharSet.Unicode,
SetLastError = true, ExactSpelling = true)]
public static extern IntPtr CreateWindowExW(WindowStylesEx dwExStyle,
ushort lpszClassName,
[MarshalAs(UnmanagedType.LPStr)] string lpszWindowName,
[MarshalAs(UnmanagedType.LPTStr)] string lpszWindowName,
WindowStyles style,
int x, int y, int width, int height,
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);
}
}

View File

@@ -1,5 +1,6 @@
using NTwain.Data;
using NTwain.Internals;
using NTwain.Resources;
using System;
using System.Diagnostics;
using System.Reflection;
@@ -57,7 +58,7 @@ namespace NTwain
if (image) dg |= DataGroups.Image;
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;
return this;
}
@@ -138,7 +139,7 @@ namespace NTwain
case PlatformID.Unix:
case PlatformID.MacOSX:
default:
throw new PlatformNotSupportedException($"This platform {_platform} is not supported.");
throw new PlatformNotSupportedException(string.Format(MsgText.PlatformNotSupported, _platform));
}
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,
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})");
BeginInvoke(() =>
InternalBeginInvoke(() =>
{
Debug.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}: in BeginInvoke {nameof(Handle32BitCallback)}({dg}, {dat}, {msg}, {data})");
HandleSourceMsg(msg);

View File

@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
namespace NTwain
{
@@ -53,8 +54,11 @@ namespace NTwain
/// <param name="e"></param>
internal protected virtual void OnSourceDisabled(SourceDisabledEventArgs e)
{
var handler = SourceDisabled;
handler?.Invoke(this, e);
ExternalInvoke(() =>
{
var handler = SourceDisabled;
handler?.Invoke(this, e);
});
}
/// <summary>
@@ -68,8 +72,11 @@ namespace NTwain
/// <param name="e"></param>
protected virtual void OnDeviceEventReceived(DeviceEventArgs e)
{
var handler = DeviceEventReceived;
handler?.Invoke(this, e);
ExternalInvoke(() =>
{
var handler = DeviceEventReceived;
handler?.Invoke(this, e);
});
}
/// <summary>
@@ -83,8 +90,11 @@ namespace NTwain
/// <param name="e"></param>
protected virtual void OnTransferReady(TransferReadyEventArgs e)
{
var handler = TransferReady;
handler?.Invoke(this, e);
ExternalInvoke(() =>
{
var handler = TransferReady;
handler?.Invoke(this, e);
});
}
@@ -92,6 +102,21 @@ namespace NTwain
///// Occurs when data has been transferred.
///// </summary>
//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>
/// Occurs when an error has been encountered during transfer.
/// </summary>
@@ -103,8 +128,11 @@ namespace NTwain
/// <param name="e"></param>
protected virtual void OnTransferError(TransferErrorEventArgs e)
{
var handler = TransferError;
handler?.Invoke(this, e);
ExternalInvoke(() =>
{
var handler = TransferError;
handler?.Invoke(this, e);
});
}
@@ -120,8 +148,11 @@ namespace NTwain
/// <param name="propertyName">Name of the property.</param>
protected void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
ExternalInvoke(() =>
{
var handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
});
}
}
}

View File

@@ -1,4 +1,5 @@
using NTwain.Data;
using NTwain.Resources;
using NTwain.Threading;
using NTwain.Triplets;
using System;
@@ -19,43 +20,89 @@ namespace NTwain
{
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
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;
// for windows only
readonly IThreadContext _internalContext;
readonly WinMsgLoop _winMsgLoop;
private IntPtr _hWnd;
private IThreadContext _externalContext;
/// <summary>
/// Constructs a new <see cref="TwainSession"/>.
/// </summary>
/// <param name="config"></param>
/// <exception cref="ArgumentNullException"></exception>
public TwainSession(TwainConfig config)
{
Config = config;
Config = config ?? throw new ArgumentNullException(nameof(config));
SetSynchronizationContext(SynchronizationContext.Current);
switch (config.Platform)
{
case PlatformID.MacOSX:
case PlatformID.Unix:
_internalContext = new DispatcherLoop(this);
break;
default:
_winMsgLoop = new WinMsgLoop(this);
_internalContext = new WinMsgLoop(this);
_callback32Delegate = new Callback32(Handle32BitCallback);
break;
}
}
public void Invoke(Action action)
/// <summary>
/// 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();
}
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();
}
@@ -70,7 +117,7 @@ namespace NTwain
var rc = DGControl.Parent.OpenDSM(hWnd);
if (rc == ReturnCode.Success)
{
_winMsgLoop?.Start();
_internalContext?.Start();
}
return rc;
}
@@ -84,7 +131,7 @@ namespace NTwain
var rc = DGControl.Parent.CloseDSM(_hWnd);
if (rc == ReturnCode.Success)
{
_winMsgLoop?.Stop();
_internalContext?.Stop();
}
return rc;
}
@@ -116,6 +163,14 @@ namespace NTwain
rc = DGControl.Identity.CloseDS(CurrentSource.Identity32);
if (rc != ReturnCode.Success) return rc;
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;
@@ -210,7 +265,7 @@ namespace NTwain
{
if (value.Session != this)
{
throw new InvalidOperationException("Source is not from this session.");
throw new InvalidOperationException(MsgText.SourceNotThisSession);
}
var rc = DGControl.Identity.Set(value);
RaisePropertyChanged(nameof(DefaultSource));
@@ -274,7 +329,7 @@ namespace NTwain
/// </returns>
public override string ToString()
{
return $"Session: {State}";
return State.ToString();
}
}
}