mirror of
https://github.com/soukoku/ntwain.git
synced 2026-02-25 13:04:07 +08:00
Merge branch 'v4' of https://github.com/soukoku/ntwain into v4
This commit is contained in:
+5
-1
@@ -18,7 +18,11 @@
|
|||||||
<File Path="twain-doc\twain2.5.h" />
|
<File Path="twain-doc\twain2.5.h" />
|
||||||
</Folder>
|
</Folder>
|
||||||
<Folder Name="/Samples/">
|
<Folder Name="/Samples/">
|
||||||
<Project Path="samples/KodakTester/KodakTester.csproj" />
|
<Project Path="samples/KodakTester/KodakTester.csproj">
|
||||||
|
<Platform Solution="*|ARM64" Project="x64" />
|
||||||
|
<Platform Solution="*|x64" Project="x64" />
|
||||||
|
<Platform Solution="*|x86" Project="x86" />
|
||||||
|
</Project>
|
||||||
<Project Path="samples/WinForm32/WinForm32.csproj" />
|
<Project Path="samples/WinForm32/WinForm32.csproj" />
|
||||||
<Project Path="samples/WinForm64/WinForm64.csproj" />
|
<Project Path="samples/WinForm64/WinForm64.csproj" />
|
||||||
<Project Path="samples/WinUI3/WinUI3.csproj" Id="9876f110-22e9-4813-8f5d-b317c152885f">
|
<Project Path="samples/WinUI3/WinUI3.csproj" Id="9876f110-22e9-4813-8f5d-b317c152885f">
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<ProjectReference Include="..\..\src\NTwain\NTwain.csproj" />
|
<ProjectReference Include="..\..\src\NTwain\NTwain.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Magick.NET-Q8-AnyCPU" Version="14.10.1" />
|
<PackageReference Include="Magick.NET-Q8-AnyCPU" Version="14.10.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -141,6 +141,5 @@ internal class Program
|
|||||||
{
|
{
|
||||||
Console.WriteLine($"BUMMER! No twain data #{++xferCount} on thread {Environment.CurrentManagedThreadId}.");
|
Console.WriteLine($"BUMMER! No twain data #{++xferCount} on thread {Environment.CurrentManagedThreadId}.");
|
||||||
}
|
}
|
||||||
e.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<UseWindowsForms>true</UseWindowsForms>
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<PlatformTarget>x86</PlatformTarget>
|
<Platforms>x86;x64</Platforms>
|
||||||
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
|
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
|
||||||
<ApplicationVisualStyles>true</ApplicationVisualStyles>
|
<ApplicationVisualStyles>true</ApplicationVisualStyles>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -98,7 +98,6 @@ public partial class MainForm : Form
|
|||||||
var path = info.FileName.ToString();
|
var path = info.FileName.ToString();
|
||||||
LogIt($"Received {e.ImageInfo.PixelType} {info.Format} in {e.ImageInfo.Compression} compressed file {path}");
|
LogIt($"Received {e.ImageInfo.PixelType} {info.Format} in {e.ImageInfo.Compression} compressed file {path}");
|
||||||
}
|
}
|
||||||
e.Dispose();
|
|
||||||
LogIt("");
|
LogIt("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using NTwain;
|
using NTwain;
|
||||||
using NTwain.Caps;
|
|
||||||
using NTwain.Data;
|
using NTwain.Data;
|
||||||
using NTwain.Events;
|
using NTwain.Events;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Drawing.Imaging;
|
using System.Drawing.Imaging;
|
||||||
@@ -20,7 +18,6 @@ namespace WinFormSample;
|
|||||||
|
|
||||||
public partial class Form1 : Form
|
public partial class Form1 : Form
|
||||||
{
|
{
|
||||||
bool useDiyPump = true;
|
|
||||||
TwainAppSession twain;
|
TwainAppSession twain;
|
||||||
readonly string saveFolder;
|
readonly string saveFolder;
|
||||||
readonly Stopwatch watch = new();
|
readonly Stopwatch watch = new();
|
||||||
@@ -134,20 +131,22 @@ public partial class Form1 : Form
|
|||||||
if (_useThreadForImag)
|
if (_useThreadForImag)
|
||||||
{
|
{
|
||||||
// bad thread example but whatev. should use a dedicated thread of some sort for real
|
// bad thread example but whatev. should use a dedicated thread of some sort for real
|
||||||
|
var data = e.TakeDataOwnership();
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
HandleTransferredData(e);
|
HandleTransferredData(data);
|
||||||
|
data?.Dispose();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HandleTransferredData(e);
|
HandleTransferredData(e.Data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleTransferredData(TransferredEventArgs e)
|
private void HandleTransferredData(BufferedData? data)
|
||||||
{
|
{
|
||||||
if (e.Data != null)
|
if (data != null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -156,7 +155,7 @@ public partial class Form1 : Form
|
|||||||
|
|
||||||
if (_useSystemDrawing)
|
if (_useSystemDrawing)
|
||||||
{
|
{
|
||||||
using (var img = Image.FromStream(e.Data.AsStream()))
|
using (var img = Image.FromStream(data.AsStream()))
|
||||||
{
|
{
|
||||||
if (img.PixelFormat == System.Drawing.Imaging.PixelFormat.Format1bppIndexed ||
|
if (img.PixelFormat == System.Drawing.Imaging.PixelFormat.Format1bppIndexed ||
|
||||||
img.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed)
|
img.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed)
|
||||||
@@ -177,7 +176,7 @@ public partial class Form1 : Form
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
using (var img = new ImageMagick.MagickImage(e.Data.AsSpan()))
|
using (var img = new ImageMagick.MagickImage(data.AsSpan()))
|
||||||
{
|
{
|
||||||
var format = ImageMagick.MagickFormat.Png;
|
var format = ImageMagick.MagickFormat.Png;
|
||||||
if (img.ColorType == ImageMagick.ColorType.Palette)
|
if (img.ColorType == ImageMagick.ColorType.Palette)
|
||||||
@@ -201,7 +200,7 @@ public partial class Form1 : Form
|
|||||||
catch { }
|
catch { }
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
e.Dispose();
|
data.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Magick.NET-Q8-x86" Version="14.10.1" />
|
<PackageReference Include="Magick.NET-Q8-x86" Version="14.10.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Magick.NET-Q8-x64" Version="14.10.1" />
|
<PackageReference Include="Magick.NET-Q8-x64" Version="14.10.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -110,10 +110,6 @@ public sealed partial class MainWindow : Window
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
finally
|
|
||||||
{
|
|
||||||
e.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Session_SourceDisabled(TwainAppSession sender, TWIdentityWrapper e)
|
private void Session_SourceDisabled(TwainAppSession sender, TWIdentityWrapper e)
|
||||||
|
|||||||
@@ -18,9 +18,9 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Magick.NET-Q8-x86" Version="14.10.1" />
|
<PackageReference Include="Magick.NET-Q8-AnyCPU" Version="14.10.2" />
|
||||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7463" />
|
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7463" />
|
||||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.251106002" />
|
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.260101001" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -2,18 +2,18 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!--change these in each release-->
|
<!--change these in each release-->
|
||||||
<VersionPrefix>4.0.0.0</VersionPrefix>
|
<VersionPrefix>4.0.0.0</VersionPrefix>
|
||||||
<VersionSuffix>beta.1</VersionSuffix>
|
<VersionSuffix>beta.4</VersionSuffix>
|
||||||
|
|
||||||
<!--keep it the same until major # changes-->
|
<!--keep it the same until major # changes-->
|
||||||
<AssemblyVersion>4.0.0.0</AssemblyVersion>
|
<AssemblyVersion>4.0.0.0</AssemblyVersion>
|
||||||
|
|
||||||
<!--even more unlikely to change-->
|
<!--even more unlikely to change-->
|
||||||
<FileVersion>$(VersionPrefix)</FileVersion>
|
<FileVersion>$(VersionPrefix)</FileVersion>
|
||||||
<PackageProjectUrl>https://github.com/soukoku/ntwain5</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/soukoku/ntwain</PackageProjectUrl>
|
||||||
<PackageTags>twain scan</PackageTags>
|
<PackageTags>twain scan</PackageTags>
|
||||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
<PackageIcon>Scanner-Icon.png</PackageIcon>
|
<PackageIcon>Scanner-Icon.png</PackageIcon>
|
||||||
<RepositoryUrl>https://github.com/soukoku/ntwain5</RepositoryUrl>
|
<RepositoryUrl>https://github.com/soukoku/ntwain</RepositoryUrl>
|
||||||
<RepositoryType>GIT</RepositoryType>
|
<RepositoryType>GIT</RepositoryType>
|
||||||
<NeutralLanguage>en-US</NeutralLanguage>
|
<NeutralLanguage>en-US</NeutralLanguage>
|
||||||
<Copyright>Eugene Wang 2012-2026</Copyright>
|
<Copyright>Eugene Wang 2012-2026</Copyright>
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ public class BufferedData : IDisposable
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_fromPool && _disposed)
|
if (_fromPool && !_disposed)
|
||||||
{
|
{
|
||||||
MemPool.Return(_buffer);
|
MemPool.Return(_buffer);
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
|
|||||||
@@ -34,11 +34,14 @@ public class TransferredEventArgs : EventArgs, IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsImage { get; }
|
public bool IsImage { get; }
|
||||||
|
|
||||||
private readonly BufferedData? _data;
|
private BufferedData? _data;
|
||||||
|
private bool _dataOwnershipTransferred;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The complete file data if memory was involved in the transfer.
|
/// Gets the transferred data.
|
||||||
/// IMPORTANT: Content of this array will not be valid once
|
/// IMPORTANT: This data is only valid during the event handler execution.
|
||||||
/// this event arg has been disposed.
|
/// If you need to keep the data after the event handler returns, call <see cref="TakeDataOwnership"/> first.
|
||||||
|
/// Otherwise, the data will be automatically disposed when the event completes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public BufferedData? Data => _data;
|
public BufferedData? Data => _data;
|
||||||
|
|
||||||
@@ -73,8 +76,51 @@ public class TransferredEventArgs : EventArgs, IDisposable
|
|||||||
return _twain.WrapInSTS(DGImage.ExtImageInfo.Get(_twain.AppIdentity, _twain.CurrentSource, ref container));
|
return _twain.WrapInSTS(DGImage.ExtImageInfo.Get(_twain.AppIdentity, _twain.CurrentSource, ref container));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transfers ownership of the in-memory data to the caller, preventing automatic disposal.
|
||||||
|
/// Use this when you need to process the data asynchronously or keep it beyond the event handler scope.
|
||||||
|
/// After calling this, you MUST manually dispose the returned BufferedData when finished.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The buffered data with transferred ownership, or null if no data exists or ownership was already transferred.
|
||||||
|
/// </returns>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// private async void OnTransferred(TwainAppSession sender, TransferredEventArgs e)
|
||||||
|
/// {
|
||||||
|
/// var data = e.TakeDataOwnership(); // Take ownership
|
||||||
|
/// if (data != null)
|
||||||
|
/// {
|
||||||
|
/// try
|
||||||
|
/// {
|
||||||
|
/// await ProcessDataAsync(data); // Can use beyond event handler
|
||||||
|
/// }
|
||||||
|
/// finally
|
||||||
|
/// {
|
||||||
|
/// data.Dispose(); // Must dispose when done
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public BufferedData? TakeDataOwnership()
|
||||||
|
{
|
||||||
|
if (_dataOwnershipTransferred || _data == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
_dataOwnershipTransferred = true;
|
||||||
|
var data = _data;
|
||||||
|
_data = null;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_data?.Dispose();
|
// Only dispose if ownership wasn't transferred
|
||||||
|
if (!_dataOwnershipTransferred && _data != null)
|
||||||
|
{
|
||||||
|
_data.Dispose();
|
||||||
|
_data = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.264">
|
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.269">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@@ -20,18 +20,17 @@ namespace NTwain.Platform;
|
|||||||
#if !NETFRAMEWORK
|
#if !NETFRAMEWORK
|
||||||
[SupportedOSPlatform("windows5.1.2600")]
|
[SupportedOSPlatform("windows5.1.2600")]
|
||||||
#endif
|
#endif
|
||||||
internal sealed class Win32MessagePump : IDisposable
|
internal sealed class Win32MessagePump
|
||||||
{
|
{
|
||||||
private const string WindowClassName = "MsgPumpParkWindow";
|
|
||||||
private const uint WM_APP_INVOKE = PInvoke.WM_APP + 1;
|
private const uint WM_APP_INVOKE = PInvoke.WM_APP + 1;
|
||||||
|
|
||||||
private readonly FreeLibrarySafeHandle _hInstance;
|
static readonly FreeLibrarySafeHandle _hInstance = PInvoke.GetModuleHandle((string?)null);
|
||||||
|
|
||||||
private readonly uint _threadId;
|
private readonly uint _threadId;
|
||||||
private HWND _mainWindow;
|
private HWND _mainWindow;
|
||||||
private bool _disposed;
|
|
||||||
|
|
||||||
// Store the delegate to prevent garbage collection
|
// Store the delegate to prevent garbage collection
|
||||||
private static WNDPROC? s_wndProc;
|
private WNDPROC _wndProc; // Instance field, not static
|
||||||
|
|
||||||
// Queue for work items posted to the UI thread
|
// Queue for work items posted to the UI thread
|
||||||
private readonly Queue<Action> _workQueue = new();
|
private readonly Queue<Action> _workQueue = new();
|
||||||
@@ -45,11 +44,15 @@ internal sealed class Win32MessagePump : IDisposable
|
|||||||
// SynchronizationContext
|
// SynchronizationContext
|
||||||
private Win32SynchronizationContext? _synchronizationContext;
|
private Win32SynchronizationContext? _synchronizationContext;
|
||||||
|
|
||||||
|
private readonly string _windowClassName;
|
||||||
|
|
||||||
public Win32MessagePump(ILogger logger)
|
public Win32MessagePump(ILogger logger)
|
||||||
{
|
{
|
||||||
_hInstance = PInvoke.GetModuleHandle((string?)null);
|
|
||||||
_threadId = PInvoke.GetCurrentThreadId();
|
_threadId = PInvoke.GetCurrentThreadId();
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_windowClassName = $"MsgPumpParkWindow_{Guid.NewGuid():N}";
|
||||||
|
_wndProc = WindowProc;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -105,33 +108,42 @@ internal sealed class Win32MessagePump : IDisposable
|
|||||||
UnregisterWindowClass();
|
UnregisterWindowClass();
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and install the SynchronizationContext
|
|
||||||
_synchronizationContext = new Win32SynchronizationContext(this);
|
_synchronizationContext = new Win32SynchronizationContext(this);
|
||||||
SynchronizationContext.SetSynchronizationContext(_synchronizationContext);
|
SynchronizationContext.SetSynchronizationContext(_synchronizationContext);
|
||||||
|
|
||||||
int exitCode = RunMessageLoop();
|
int exitCode;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
exitCode = RunMessageLoop();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (!_mainWindow.IsNull)
|
||||||
|
{
|
||||||
|
PInvoke.DestroyWindow(_mainWindow);
|
||||||
|
_mainWindow = HWND.Null;
|
||||||
|
}
|
||||||
|
|
||||||
// Clear the SynchronizationContext
|
UnregisterWindowClass();
|
||||||
SynchronizationContext.SetSynchronizationContext(null);
|
|
||||||
|
|
||||||
Dispose();
|
SynchronizationContext.SetSynchronizationContext(null);
|
||||||
|
_synchronizationContext = null;
|
||||||
|
}
|
||||||
return exitCode;
|
return exitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool RegisterWindowClass()
|
private bool RegisterWindowClass()
|
||||||
{
|
{
|
||||||
s_wndProc = WindowProc;
|
|
||||||
|
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
fixed (char* className = WindowClassName)
|
fixed (char* className = _windowClassName)
|
||||||
{
|
{
|
||||||
var wc = new WNDCLASSEXW
|
var wc = new WNDCLASSEXW
|
||||||
{
|
{
|
||||||
cbSize = (uint)Marshal.SizeOf<WNDCLASSEXW>(),
|
cbSize = (uint)Marshal.SizeOf<WNDCLASSEXW>(),
|
||||||
style = 0,
|
style = 0,
|
||||||
lpfnWndProc = s_wndProc,
|
lpfnWndProc = _wndProc,
|
||||||
cbClsExtra = 0,
|
cbClsExtra = 0,
|
||||||
cbWndExtra = 0,
|
cbWndExtra = 0,
|
||||||
hInstance = (HINSTANCE)_hInstance.DangerousGetHandle(),
|
hInstance = (HINSTANCE)_hInstance.DangerousGetHandle(),
|
||||||
@@ -156,7 +168,7 @@ internal sealed class Win32MessagePump : IDisposable
|
|||||||
|
|
||||||
private void UnregisterWindowClass()
|
private void UnregisterWindowClass()
|
||||||
{
|
{
|
||||||
PInvoke.UnregisterClass(WindowClassName, _hInstance);
|
PInvoke.UnregisterClass(_windowClassName, _hInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CreateMainWindow()
|
private bool CreateMainWindow()
|
||||||
@@ -165,7 +177,7 @@ internal sealed class Win32MessagePump : IDisposable
|
|||||||
{
|
{
|
||||||
_mainWindow = PInvoke.CreateWindowEx(
|
_mainWindow = PInvoke.CreateWindowEx(
|
||||||
0,
|
0,
|
||||||
WindowClassName,
|
_windowClassName,
|
||||||
"MsgPump Window",
|
"MsgPump Window",
|
||||||
0,
|
0,
|
||||||
0, 0, 0, 0,
|
0, 0, 0, 0,
|
||||||
@@ -203,8 +215,6 @@ internal sealed class Win32MessagePump : IDisposable
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcessWorkQueue();
|
|
||||||
|
|
||||||
if (FilterMessage(ref msg))
|
if (FilterMessage(ref msg))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -257,6 +267,8 @@ internal sealed class Win32MessagePump : IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void PostToUIThread(Action action)
|
public void PostToUIThread(Action action)
|
||||||
{
|
{
|
||||||
|
if (_mainWindow.IsNull) throw new InvalidOperationException("Message pump main window is not available.");
|
||||||
|
|
||||||
if (InvokeRequired)
|
if (InvokeRequired)
|
||||||
{
|
{
|
||||||
lock (_workQueueLock)
|
lock (_workQueueLock)
|
||||||
@@ -280,6 +292,8 @@ internal sealed class Win32MessagePump : IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Quit(int exitCode = 0)
|
public void Quit(int exitCode = 0)
|
||||||
{
|
{
|
||||||
|
if (_mainWindow.IsNull) throw new InvalidOperationException("Message pump main window is not available.");
|
||||||
|
|
||||||
if (InvokeRequired)
|
if (InvokeRequired)
|
||||||
{
|
{
|
||||||
PostToUIThread(() =>
|
PostToUIThread(() =>
|
||||||
@@ -330,8 +344,13 @@ internal sealed class Win32MessagePump : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static LRESULT WindowProc(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam)
|
private LRESULT WindowProc(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam)
|
||||||
{
|
{
|
||||||
|
if (msg == WM_APP_INVOKE)
|
||||||
|
{
|
||||||
|
ProcessWorkQueue();
|
||||||
|
return new LRESULT(0);
|
||||||
|
}
|
||||||
if (msg == PInvoke.WM_DESTROY)
|
if (msg == PInvoke.WM_DESTROY)
|
||||||
{
|
{
|
||||||
PInvoke.PostQuitMessage(0);
|
PInvoke.PostQuitMessage(0);
|
||||||
@@ -340,22 +359,6 @@ internal sealed class Win32MessagePump : IDisposable
|
|||||||
|
|
||||||
return PInvoke.DefWindowProc(hwnd, msg, wParam, lParam);
|
return PInvoke.DefWindowProc(hwnd, msg, wParam, lParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_disposed = true;
|
|
||||||
|
|
||||||
if (!_mainWindow.IsNull)
|
|
||||||
{
|
|
||||||
PInvoke.DestroyWindow(_mainWindow);
|
|
||||||
_mainWindow = HWND.Null;
|
|
||||||
}
|
|
||||||
|
|
||||||
UnregisterWindowClass();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -224,10 +224,7 @@ partial class TransferLoopThread
|
|||||||
var args = new TransferredEventArgs(info, data);
|
var args = new TransferredEventArgs(info, data);
|
||||||
_twain.RaiseTransferred(args);
|
_twain.RaiseTransferred(args);
|
||||||
}
|
}
|
||||||
catch
|
catch { }
|
||||||
{
|
|
||||||
data.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +238,7 @@ partial class TransferLoopThread
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (lockedPtr != IntPtr.Zero) _twain.MemoryManager.Unlock(lockedPtr);
|
if (lockedPtr != IntPtr.Zero) _twain.MemoryManager.Unlock(dataPtr);
|
||||||
if (dataPtr != IntPtr.Zero) _twain.MemoryManager.Free(dataPtr);
|
if (dataPtr != IntPtr.Zero) _twain.MemoryManager.Free(dataPtr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -314,10 +311,7 @@ partial class TransferLoopThread
|
|||||||
var args = new TransferredEventArgs(_twain, info, null, data);
|
var args = new TransferredEventArgs(_twain, info, null, data);
|
||||||
_twain.RaiseTransferred(args);
|
_twain.RaiseTransferred(args);
|
||||||
}
|
}
|
||||||
catch
|
catch { }
|
||||||
{
|
|
||||||
data.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -332,7 +326,7 @@ partial class TransferLoopThread
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (lockedPtr != IntPtr.Zero) _twain.MemoryManager.Unlock(lockedPtr);
|
if (lockedPtr != IntPtr.Zero) _twain.MemoryManager.Unlock(dataPtr);
|
||||||
if (dataPtr != IntPtr.Zero) _twain.MemoryManager.Free(dataPtr);
|
if (dataPtr != IntPtr.Zero) _twain.MemoryManager.Free(dataPtr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -382,7 +376,14 @@ partial class TransferLoopThread
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debugger.Break();
|
if (Debugger.IsAttached) Debugger.Break();
|
||||||
|
|
||||||
|
_twain.Logger.LogWarning(
|
||||||
|
"TransferFileImage failed unexpectedly: RC={RC}, CC={CC}",
|
||||||
|
sts.RC, sts.ConditionCode);
|
||||||
|
|
||||||
|
// Or raise error event for user to handle
|
||||||
|
_twain.RaiseTransferError(new TransferErrorEventArgs(sts, "TransferFileImage"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sts;
|
return sts;
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ partial class TwainAppSession
|
|||||||
internal void RaiseTransferred(TransferredEventArgs args)
|
internal void RaiseTransferred(TransferredEventArgs args)
|
||||||
{
|
{
|
||||||
// no wait here, app can handle at its leisure
|
// no wait here, app can handle at its leisure
|
||||||
RaiseEvent(Transferred, args);
|
RaiseEvent(Transferred, args, static e => e.Dispose());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -139,17 +139,24 @@ partial class TwainAppSession
|
|||||||
/// <typeparam name="TEventArgs">The type of event arguments.</typeparam>
|
/// <typeparam name="TEventArgs">The type of event arguments.</typeparam>
|
||||||
/// <param name="eventHandler">The event handler to invoke.</param>
|
/// <param name="eventHandler">The event handler to invoke.</param>
|
||||||
/// <param name="eventArgs">The event arguments.</param>
|
/// <param name="eventArgs">The event arguments.</param>
|
||||||
protected void RaiseEvent<TEventArgs>(EventHandler<TwainAppSession, TEventArgs>? eventHandler, TEventArgs eventArgs)
|
/// <param name="completeCallback"></param>
|
||||||
|
protected void RaiseEvent<TEventArgs>(EventHandler<TwainAppSession, TEventArgs>? eventHandler, TEventArgs eventArgs,
|
||||||
|
Action<TEventArgs>? completeCallback = null)
|
||||||
{
|
{
|
||||||
if (eventHandler == null) return;
|
if (eventHandler == null) return;
|
||||||
|
|
||||||
if (AppThreadContext != null)
|
if (AppThreadContext != null)
|
||||||
{
|
{
|
||||||
AppThreadContext.Post(_ => eventHandler(this, eventArgs), null);
|
AppThreadContext.Post(_ =>
|
||||||
|
{
|
||||||
|
eventHandler(this, eventArgs);
|
||||||
|
completeCallback?.Invoke(eventArgs);
|
||||||
|
}, null);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
eventHandler(this, eventArgs);
|
eventHandler(this, eventArgs);
|
||||||
|
completeCallback?.Invoke(eventArgs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ public partial class TwainAppSession : IDisposable
|
|||||||
DllPath.TryUseLocalDsm();
|
DllPath.TryUseLocalDsm();
|
||||||
|
|
||||||
// no need to do another lock call when using marshal alloc
|
// no need to do another lock call when using marshal alloc
|
||||||
TW_EVENT _procEvent = default;
|
_procEvent = default;
|
||||||
_procEvent.pEvent = Marshal.AllocHGlobal(Marshal.SizeOf<WinMSG>());
|
_procEvent.pEvent = Marshal.AllocHGlobal(Marshal.SizeOf<WinMSG>());
|
||||||
|
|
||||||
Thread pumpThread = new(() =>
|
Thread pumpThread = new(() =>
|
||||||
@@ -132,7 +132,6 @@ public partial class TwainAppSession : IDisposable
|
|||||||
{
|
{
|
||||||
_twainPumpForWin.RemoveMessageFilter(this);
|
_twainPumpForWin.RemoveMessageFilter(this);
|
||||||
_twainPumpForWin.Quit();
|
_twainPumpForWin.Quit();
|
||||||
_twainPumpForWin.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_procEvent.pEvent != IntPtr.Zero)
|
if (_procEvent.pEvent != IntPtr.Zero)
|
||||||
|
|||||||
Reference in New Issue
Block a user