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" />
|
||||
</Folder>
|
||||
<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/WinForm64/WinForm64.csproj" />
|
||||
<Project Path="samples/WinUI3/WinUI3.csproj" Id="9876f110-22e9-4813-8f5d-b317c152885f">
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<ProjectReference Include="..\..\src\NTwain\NTwain.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Magick.NET-Q8-AnyCPU" Version="14.10.1" />
|
||||
<PackageReference Include="Magick.NET-Q8-AnyCPU" Version="14.10.2" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
@@ -141,6 +141,5 @@ internal class Program
|
||||
{
|
||||
Console.WriteLine($"BUMMER! No twain data #{++xferCount} on thread {Environment.CurrentManagedThreadId}.");
|
||||
}
|
||||
e.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<Platforms>x86;x64</Platforms>
|
||||
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
|
||||
<ApplicationVisualStyles>true</ApplicationVisualStyles>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -98,7 +98,6 @@ public partial class MainForm : Form
|
||||
var path = info.FileName.ToString();
|
||||
LogIt($"Received {e.ImageInfo.PixelType} {info.Format} in {e.ImageInfo.Compression} compressed file {path}");
|
||||
}
|
||||
e.Dispose();
|
||||
LogIt("");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
using Microsoft.Win32;
|
||||
using NTwain;
|
||||
using NTwain.Caps;
|
||||
using NTwain.Data;
|
||||
using NTwain.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
@@ -20,7 +18,6 @@ namespace WinFormSample;
|
||||
|
||||
public partial class Form1 : Form
|
||||
{
|
||||
bool useDiyPump = true;
|
||||
TwainAppSession twain;
|
||||
readonly string saveFolder;
|
||||
readonly Stopwatch watch = new();
|
||||
@@ -134,20 +131,22 @@ public partial class Form1 : Form
|
||||
if (_useThreadForImag)
|
||||
{
|
||||
// bad thread example but whatev. should use a dedicated thread of some sort for real
|
||||
var data = e.TakeDataOwnership();
|
||||
Task.Run(() =>
|
||||
{
|
||||
HandleTransferredData(e);
|
||||
HandleTransferredData(data);
|
||||
data?.Dispose();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleTransferredData(e);
|
||||
HandleTransferredData(e.Data);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleTransferredData(TransferredEventArgs e)
|
||||
private void HandleTransferredData(BufferedData? data)
|
||||
{
|
||||
if (e.Data != null)
|
||||
if (data != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -156,7 +155,7 @@ public partial class Form1 : Form
|
||||
|
||||
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 ||
|
||||
img.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed)
|
||||
@@ -177,7 +176,7 @@ public partial class Form1 : Form
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var img = new ImageMagick.MagickImage(e.Data.AsSpan()))
|
||||
using (var img = new ImageMagick.MagickImage(data.AsSpan()))
|
||||
{
|
||||
var format = ImageMagick.MagickFormat.Png;
|
||||
if (img.ColorType == ImageMagick.ColorType.Palette)
|
||||
@@ -201,7 +200,7 @@ public partial class Form1 : Form
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
e.Dispose();
|
||||
data.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Magick.NET-Q8-x86" Version="14.10.1" />
|
||||
<PackageReference Include="Magick.NET-Q8-x86" Version="14.10.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Magick.NET-Q8-x64" Version="14.10.1" />
|
||||
<PackageReference Include="Magick.NET-Q8-x64" Version="14.10.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -110,10 +110,6 @@ public sealed partial class MainWindow : Window
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
e.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void Session_SourceDisabled(TwainAppSession sender, TWIdentityWrapper e)
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
</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.WindowsAppSDK" Version="1.8.251106002" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.260101001" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
<PropertyGroup>
|
||||
<!--change these in each release-->
|
||||
<VersionPrefix>4.0.0.0</VersionPrefix>
|
||||
<VersionSuffix>beta.1</VersionSuffix>
|
||||
<VersionSuffix>beta.4</VersionSuffix>
|
||||
|
||||
<!--keep it the same until major # changes-->
|
||||
<AssemblyVersion>4.0.0.0</AssemblyVersion>
|
||||
|
||||
<!--even more unlikely to change-->
|
||||
<FileVersion>$(VersionPrefix)</FileVersion>
|
||||
<PackageProjectUrl>https://github.com/soukoku/ntwain5</PackageProjectUrl>
|
||||
<PackageProjectUrl>https://github.com/soukoku/ntwain</PackageProjectUrl>
|
||||
<PackageTags>twain scan</PackageTags>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageIcon>Scanner-Icon.png</PackageIcon>
|
||||
<RepositoryUrl>https://github.com/soukoku/ntwain5</RepositoryUrl>
|
||||
<RepositoryUrl>https://github.com/soukoku/ntwain</RepositoryUrl>
|
||||
<RepositoryType>GIT</RepositoryType>
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
<Copyright>Eugene Wang 2012-2026</Copyright>
|
||||
|
||||
@@ -77,7 +77,7 @@ public class BufferedData : IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_fromPool && _disposed)
|
||||
if (_fromPool && !_disposed)
|
||||
{
|
||||
MemPool.Return(_buffer);
|
||||
_disposed = true;
|
||||
|
||||
@@ -34,11 +34,14 @@ public class TransferredEventArgs : EventArgs, IDisposable
|
||||
/// </summary>
|
||||
public bool IsImage { get; }
|
||||
|
||||
private readonly BufferedData? _data;
|
||||
private BufferedData? _data;
|
||||
private bool _dataOwnershipTransferred;
|
||||
|
||||
/// <summary>
|
||||
/// The complete file data if memory was involved in the transfer.
|
||||
/// IMPORTANT: Content of this array will not be valid once
|
||||
/// this event arg has been disposed.
|
||||
/// Gets the transferred data.
|
||||
/// IMPORTANT: This data is only valid during the event handler execution.
|
||||
/// 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>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <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()
|
||||
{
|
||||
_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" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.264">
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.269">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -20,18 +20,17 @@ namespace NTwain.Platform;
|
||||
#if !NETFRAMEWORK
|
||||
[SupportedOSPlatform("windows5.1.2600")]
|
||||
#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 readonly FreeLibrarySafeHandle _hInstance;
|
||||
static readonly FreeLibrarySafeHandle _hInstance = PInvoke.GetModuleHandle((string?)null);
|
||||
|
||||
private readonly uint _threadId;
|
||||
private HWND _mainWindow;
|
||||
private bool _disposed;
|
||||
|
||||
// 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
|
||||
private readonly Queue<Action> _workQueue = new();
|
||||
@@ -45,11 +44,15 @@ internal sealed class Win32MessagePump : IDisposable
|
||||
// SynchronizationContext
|
||||
private Win32SynchronizationContext? _synchronizationContext;
|
||||
|
||||
private readonly string _windowClassName;
|
||||
|
||||
public Win32MessagePump(ILogger logger)
|
||||
{
|
||||
_hInstance = PInvoke.GetModuleHandle((string?)null);
|
||||
_threadId = PInvoke.GetCurrentThreadId();
|
||||
_logger = logger;
|
||||
_windowClassName = $"MsgPumpParkWindow_{Guid.NewGuid():N}";
|
||||
_wndProc = WindowProc;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -106,32 +109,41 @@ internal sealed class Win32MessagePump : IDisposable
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create and install the SynchronizationContext
|
||||
_synchronizationContext = new Win32SynchronizationContext(this);
|
||||
SynchronizationContext.SetSynchronizationContext(_synchronizationContext);
|
||||
|
||||
int exitCode = RunMessageLoop();
|
||||
int exitCode;
|
||||
try
|
||||
{
|
||||
exitCode = RunMessageLoop();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!_mainWindow.IsNull)
|
||||
{
|
||||
PInvoke.DestroyWindow(_mainWindow);
|
||||
_mainWindow = HWND.Null;
|
||||
}
|
||||
|
||||
UnregisterWindowClass();
|
||||
|
||||
// Clear the SynchronizationContext
|
||||
SynchronizationContext.SetSynchronizationContext(null);
|
||||
|
||||
Dispose();
|
||||
_synchronizationContext = null;
|
||||
}
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
private bool RegisterWindowClass()
|
||||
{
|
||||
s_wndProc = WindowProc;
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (char* className = WindowClassName)
|
||||
fixed (char* className = _windowClassName)
|
||||
{
|
||||
var wc = new WNDCLASSEXW
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf<WNDCLASSEXW>(),
|
||||
style = 0,
|
||||
lpfnWndProc = s_wndProc,
|
||||
lpfnWndProc = _wndProc,
|
||||
cbClsExtra = 0,
|
||||
cbWndExtra = 0,
|
||||
hInstance = (HINSTANCE)_hInstance.DangerousGetHandle(),
|
||||
@@ -156,7 +168,7 @@ internal sealed class Win32MessagePump : IDisposable
|
||||
|
||||
private void UnregisterWindowClass()
|
||||
{
|
||||
PInvoke.UnregisterClass(WindowClassName, _hInstance);
|
||||
PInvoke.UnregisterClass(_windowClassName, _hInstance);
|
||||
}
|
||||
|
||||
private bool CreateMainWindow()
|
||||
@@ -165,7 +177,7 @@ internal sealed class Win32MessagePump : IDisposable
|
||||
{
|
||||
_mainWindow = PInvoke.CreateWindowEx(
|
||||
0,
|
||||
WindowClassName,
|
||||
_windowClassName,
|
||||
"MsgPump Window",
|
||||
0,
|
||||
0, 0, 0, 0,
|
||||
@@ -203,8 +215,6 @@ internal sealed class Win32MessagePump : IDisposable
|
||||
return -1;
|
||||
}
|
||||
|
||||
ProcessWorkQueue();
|
||||
|
||||
if (FilterMessage(ref msg))
|
||||
{
|
||||
continue;
|
||||
@@ -257,6 +267,8 @@ internal sealed class Win32MessagePump : IDisposable
|
||||
/// </summary>
|
||||
public void PostToUIThread(Action action)
|
||||
{
|
||||
if (_mainWindow.IsNull) throw new InvalidOperationException("Message pump main window is not available.");
|
||||
|
||||
if (InvokeRequired)
|
||||
{
|
||||
lock (_workQueueLock)
|
||||
@@ -280,6 +292,8 @@ internal sealed class Win32MessagePump : IDisposable
|
||||
/// </summary>
|
||||
public void Quit(int exitCode = 0)
|
||||
{
|
||||
if (_mainWindow.IsNull) throw new InvalidOperationException("Message pump main window is not available.");
|
||||
|
||||
if (InvokeRequired)
|
||||
{
|
||||
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)
|
||||
{
|
||||
PInvoke.PostQuitMessage(0);
|
||||
@@ -340,22 +359,6 @@ internal sealed class Win32MessagePump : IDisposable
|
||||
|
||||
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>
|
||||
|
||||
@@ -224,10 +224,7 @@ partial class TransferLoopThread
|
||||
var args = new TransferredEventArgs(info, data);
|
||||
_twain.RaiseTransferred(args);
|
||||
}
|
||||
catch
|
||||
{
|
||||
data.Dispose();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +238,7 @@ partial class TransferLoopThread
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -314,10 +311,7 @@ partial class TransferLoopThread
|
||||
var args = new TransferredEventArgs(_twain, info, null, data);
|
||||
_twain.RaiseTransferred(args);
|
||||
}
|
||||
catch
|
||||
{
|
||||
data.Dispose();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
|
||||
@@ -332,7 +326,7 @@ partial class TransferLoopThread
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -382,7 +376,14 @@ partial class TransferLoopThread
|
||||
}
|
||||
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;
|
||||
|
||||
@@ -66,7 +66,7 @@ partial class TwainAppSession
|
||||
internal void RaiseTransferred(TransferredEventArgs args)
|
||||
{
|
||||
// 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>
|
||||
/// <param name="eventHandler">The event handler to invoke.</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 (AppThreadContext != null)
|
||||
{
|
||||
AppThreadContext.Post(_ => eventHandler(this, eventArgs), null);
|
||||
AppThreadContext.Post(_ =>
|
||||
{
|
||||
eventHandler(this, eventArgs);
|
||||
completeCallback?.Invoke(eventArgs);
|
||||
}, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
eventHandler(this, eventArgs);
|
||||
completeCallback?.Invoke(eventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ public partial class TwainAppSession : IDisposable
|
||||
DllPath.TryUseLocalDsm();
|
||||
|
||||
// no need to do another lock call when using marshal alloc
|
||||
TW_EVENT _procEvent = default;
|
||||
_procEvent = default;
|
||||
_procEvent.pEvent = Marshal.AllocHGlobal(Marshal.SizeOf<WinMSG>());
|
||||
|
||||
Thread pumpThread = new(() =>
|
||||
@@ -132,7 +132,6 @@ public partial class TwainAppSession : IDisposable
|
||||
{
|
||||
_twainPumpForWin.RemoveMessageFilter(this);
|
||||
_twainPumpForWin.Quit();
|
||||
_twainPumpForWin.Dispose();
|
||||
}
|
||||
|
||||
if (_procEvent.pEvent != IntPtr.Zero)
|
||||
|
||||
Reference in New Issue
Block a user