Sdk: Win32 worker polish

This commit is contained in:
Ben Olden-Cooligan 2023-08-25 19:14:26 -07:00
parent 657a05c2d2
commit 65aec41038
13 changed files with 87 additions and 24 deletions

View File

@ -38,7 +38,7 @@ public class DesktopController
private bool _initialized;
private bool _suspended;
public DesktopController(ScanningContext scanningContext, UiImageList imageList,
internal DesktopController(ScanningContext scanningContext, UiImageList imageList,
RecoveryStorageManager recoveryStorageManager, ThumbnailController thumbnailController,
OperationProgress operationProgress, Naps2Config config, IOperationFactory operationFactory,
StillImage stillImage,

View File

@ -17,7 +17,7 @@ public class AboutForm : EtoDialogBase
private readonly UpdateChecker _updateChecker;
private readonly CheckBox _enableDebugLogging = C.CheckBox(UiStrings.EnableDebugLogging);
public AboutForm(Naps2Config config, UpdateChecker updateChecker, IWorkerFactory workerFactory)
public AboutForm(Naps2Config config, UpdateChecker updateChecker)
: base(config)
{
_donateButton = EtoPlatform.Current.AccessibleImageButton(

View File

@ -0,0 +1,36 @@
using NAPS2.Images.Gdi;
using NAPS2.Scan;
namespace NAPS2.Sdk.Samples;
public class TwainSample
{
public static async Task Run()
{
// Scanning with TWAIN on Windows must happen from a 32-bit process. You can do this by building your exe as
// 32-bit, but the better solution is to install the NAPS2.Sdk.Worker.Win32 Nuget package, which includes a
// pre-compiled 32-bit NAPS2.Worker.exe. Then you only need to call ScanningContext.SetUpWin32Worker and you
// should be able to scan with TWAIN.
//
// If you want to use a worker process but don't want to use a pre-compiled exe (or want to set up your own
// logging etc), you can also build your own worker exe with the same name (and call WorkerServer.Run in its
// Main method).
using var scanningContext = new ScanningContext(new GdiImageContext());
// Set up the worker; this includes starting a worker process in the background so it will be ready to respond
// when we need it
scanningContext.SetUpWin32Worker();
var controller = new ScanController(scanningContext);
// As we're not using the default (WIA) driver, we need to specify it when listing devices or scanning
ScanDevice device = (await controller.GetDeviceList(Driver.Twain)).First();
var options = new ScanOptions { Device = device, Driver = Driver.Twain };
await foreach (var image in controller.Scan(options))
{
Console.WriteLine("Scanned a page!");
}
}
}

View File

@ -25,4 +25,8 @@
<ProjectReference Include="..\NAPS2.Sdk\NAPS2.Sdk.csproj" />
<PackageReference Include="LargeAddressAware" Version="1.0.3" />
</ItemGroup>
<ItemGroup>
<None Remove="*.targets" />
</ItemGroup>
</Project>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>net462;net6</TargetFrameworks>
<RootNamespace>NAPS2.Sdk.Worker.Win32</RootNamespace>
<RootNamespace>NAPS2.Sdk.Worker</RootNamespace>
<AssemblyName>NAPS2.Sdk.Worker.Win32</AssemblyName>
<Title>NAPS2.Sdk.Worker.Win32</Title>
@ -17,7 +17,7 @@
<ItemGroup>
<Compile Remove="*.*" />
<Content Include="NAPS2.Sdk.Worker.targets" PackagePath="build/NAPS2.Sdk.Worker.targets" />
<Content Include="NAPS2.Sdk.Worker.Win32.targets" PackagePath="build/NAPS2.Sdk.Worker.Win32.targets" />
<None Include="bin\Release\net6\win-x86\publish\NAPS2.Worker.exe">
<Link>lib/NAPS2.Worker.exe</Link>
<Pack>true</Pack>

View File

@ -5,7 +5,7 @@ namespace NAPS2.Remoting.Worker;
/// <summary>
/// A factory interface to spawn NAPS2.Worker.exe instances as needed.
/// </summary>
public interface IWorkerFactory : IDisposable
internal interface IWorkerFactory : IDisposable
{
void Init(ScanningContext scanningContext, WorkerFactoryInitOptions? options = null);
WorkerContext Create(ScanningContext scanningContext, WorkerType workerType);

View File

@ -7,7 +7,7 @@ namespace NAPS2.Remoting.Worker;
/// <summary>
/// A class storing the objects the client needs to use a NAPS2.Worker.exe instance.
/// </summary>
public class WorkerContext : IDisposable
internal class WorkerContext : IDisposable
{
/// <summary>
/// Timeout after attempting to normally stop a worker before it is killed.

View File

@ -9,12 +9,10 @@ namespace NAPS2.Remoting.Worker;
/// <summary>
/// A class to manage the lifecycle of worker processes and hook up the named pipe channels.
/// </summary>
public class WorkerFactory : IWorkerFactory
internal class WorkerFactory : IWorkerFactory
{
public const string PIPE_NAME_FORMAT = "NAPS2.Worker.{0}";
private readonly string _nativeWorkerExePath;
private readonly string? _winX86WorkerExePath;
private const int TAKE_WORKER_TIMEOUT = 10_000;
private readonly Dictionary<string, string> _environmentVariables;
private Dictionary<WorkerType, BlockingCollection<WorkerContext>>? _workerQueues;
@ -53,24 +51,27 @@ public class WorkerFactory : IWorkerFactory
public WorkerFactory(string nativeWorkerExePath, string? winX86WorkerExePath = null,
Dictionary<string, string>? environmentVariables = null)
{
_nativeWorkerExePath = nativeWorkerExePath;
_winX86WorkerExePath = winX86WorkerExePath;
NativeWorkerExePath = nativeWorkerExePath;
WinX86WorkerExePath = winX86WorkerExePath;
_environmentVariables = environmentVariables ?? new Dictionary<string, string>();
}
public string NativeWorkerExePath { get; }
public string? WinX86WorkerExePath { get; }
private Process StartWorkerProcess(WorkerType workerType)
{
var parentId = Process.GetCurrentProcess().Id;
ProcessStartInfo startInfo;
if (workerType == WorkerType.WinX86)
{
if (!PlatformCompat.System.SupportsWinX86Worker || _winX86WorkerExePath == null)
if (!PlatformCompat.System.SupportsWinX86Worker || WinX86WorkerExePath == null)
{
throw new InvalidOperationException("Unexpected worker configuration");
}
startInfo = new ProcessStartInfo
{
FileName = _winX86WorkerExePath,
FileName = WinX86WorkerExePath,
Arguments = $"{parentId}",
RedirectStandardInput = true,
RedirectStandardOutput = true,
@ -81,7 +82,7 @@ public class WorkerFactory : IWorkerFactory
{
startInfo = new ProcessStartInfo
{
FileName = _nativeWorkerExePath,
FileName = NativeWorkerExePath,
Arguments = $"worker {parentId}",
RedirectStandardInput = true,
RedirectStandardOutput = true,
@ -150,18 +151,22 @@ public class WorkerFactory : IWorkerFactory
private WorkerContext NextWorker(ScanningContext scanningContext, WorkerType workerType)
{
StartWorkerService(scanningContext, workerType);
return _workerQueues![workerType]!.Take();
if (!_workerQueues![workerType]!.TryTake(out var worker, TAKE_WORKER_TIMEOUT))
{
throw new InvalidOperationException("Could not start a worker process; see logs for details");
}
return worker;
}
public void Init(ScanningContext scanningContext, WorkerFactoryInitOptions? options)
public void Init(ScanningContext scanningContext, WorkerFactoryInitOptions? options = null)
{
if (!File.Exists(_nativeWorkerExePath))
if (!File.Exists(NativeWorkerExePath))
{
scanningContext.Logger.LogDebug($"Native worker exe does not exist: {_nativeWorkerExePath}");
scanningContext.Logger.LogDebug($"Native worker exe does not exist: {NativeWorkerExePath}");
}
if (_winX86WorkerExePath != null && !File.Exists(_winX86WorkerExePath))
if (WinX86WorkerExePath != null && !File.Exists(WinX86WorkerExePath))
{
scanningContext.Logger.LogDebug($"WinX86 worker exe does not exist: {_winX86WorkerExePath}");
scanningContext.Logger.LogDebug($"WinX86 worker exe does not exist: {WinX86WorkerExePath}");
}
options ??= new WorkerFactoryInitOptions();

View File

@ -1,6 +1,6 @@
namespace NAPS2.Remoting.Worker;
public class WorkerFactoryInitOptions
internal class WorkerFactoryInitOptions
{
public bool StartSpareWorkers { get; set; } = true;
}

View File

@ -1,6 +1,6 @@
namespace NAPS2.Remoting.Worker;
public enum WorkerType
internal enum WorkerType
{
Native,
WinX86

View File

@ -45,7 +45,7 @@ public class ScanningContext : IDisposable
/// Gets or sets the context's WorkerFactory. This is required for some operations that need to happen in a worker
/// process (e.g. to scan with 32-bit TWAIN from a 64-bit process).
/// </summary>
public IWorkerFactory? WorkerFactory { get; set; }
internal IWorkerFactory? WorkerFactory { get; set; }
/// <summary>
/// Gets or sets the context's OcrEngine. This is used to perform the OCR (optical character recognition) operation

View File

@ -0,0 +1,18 @@
using NAPS2.Remoting.Worker;
namespace NAPS2.Scan;
public static class ScanningContextExtensions
{
public static void SetUpWin32Worker(this ScanningContext scanningContext)
{
var workerFactory = WorkerFactory.CreateDefault();
if (!File.Exists(workerFactory.WinX86WorkerExePath))
{
throw new InvalidOperationException(
"Could not find NAPS2.Worker.exe; have you installed the NAPS2.Sdk.Worker.Win32 Nuget package?");
}
workerFactory.Init(scanningContext);
scanningContext.WorkerFactory = workerFactory;
}
}