mirror of
https://github.com/soukoku/ntwain.git
synced 2025-11-24 08:47:06 +08:00
Merge pull request #3 from soukoku/v4-dev
More thread marshaling stuff.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
2265
size-test/twain.h
Normal file
File diff suppressed because it is too large
Load Diff
122
src/NTwain/Data/Containers.cs
Normal file
122
src/NTwain/Data/Containers.cs
Normal 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 device’s "power-on" value for the capability. If the application is
|
||||
/// performing a MSG_SET operation and isn’t 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;
|
||||
}
|
||||
}
|
||||
@@ -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)]
|
||||
|
||||
@@ -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
|
||||
@@ -2051,42 +1957,7 @@ namespace NTwain.Data
|
||||
/// </summary>
|
||||
public EndXferJob EndOfJob { get { return (EndXferJob)_eOJ; } }
|
||||
}
|
||||
|
||||
|
||||
// /// <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 device’s "power-on" value for the capability. If the application is
|
||||
// /// performing a MSG_SET operation and isn’t 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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
28
src/NTwain/Internals/ExceptionExtensions.cs
Normal file
28
src/NTwain/Internals/ExceptionExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
27
src/NTwain/Resources/MsgText.Designer.cs
generated
27
src/NTwain/Resources/MsgText.Designer.cs
generated
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
|
||||
110
src/NTwain/Threading/DispatcherLoop.cs
Normal file
110
src/NTwain/Threading/DispatcherLoop.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/NTwain/Threading/IThreadContext.cs
Normal file
32
src/NTwain/Threading/IThreadContext.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
83
src/NTwain/Threading/UIThreadContext.cs
Normal file
83
src/NTwain/Threading/UIThreadContext.cs
Normal 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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user