Fix Win7 launch crash: keep service types off the startup path
Reported: RemSound no longer launches at all on Windows 7 since the send-only service was added. Cause: RemSoundService derives from ServiceBase (System.ServiceProcess), and Program.Main called RemSoundService.RunAsService() directly in its body. The runtime resolves every type a method names when it JIT-compiles that method -- so the moment Main was compiled, at the very start of every launch and before any argument was read, it force-loaded System.ServiceProcess. That assembly won't load on Windows 7 under the .NET 10 runtime, so Main failed to compile and the app died with no window. net10 has always been the target, so this was a pure regression from the service work, not a framework change. Fix: - Move the whole service-verb dispatch into a separate ServiceEntry class. Program.Main now only calls it (a) after a cheap check that uses only inlined const verb strings, and (b) only when a service verb is actually present. A normal launch never JIT-compiles anything that names a service type, so System.ServiceProcess is never loaded. Verified empirically: a normal launch loads 107 modules, none of them System.ServiceProcess. - Gate the Service menu to Windows 10+ (OperatingSystem.IsWindowsVersionAtLeast), mirroring how the "capture individual apps" feature is gated. On Win7/8 the menu isn't shown and no service code is reachable. Made the status-query handler defensive too, so a query failure can never crash the menu. New self-test "Service verb gate": normal launches (no args, --silent, --profile, --connect, --minimized, --config-dir) are never treated as a service invocation; all five service verbs are recognised case-insensitively; and deciding a normal launch loads no service assembly. Gate 29/29. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
26903950f7
commit
9392cc1fcc
@@ -2182,7 +2182,13 @@ public sealed class MainForm : Form
|
||||
// and the arrow keys.
|
||||
menu.Items.Add(fileMenu);
|
||||
menu.Items.Add(recordMenu);
|
||||
menu.Items.Add(BuildServiceMenu());
|
||||
// The send-only Windows service is a Windows 10+ feature: it was built and tested there, and on
|
||||
// older Windows the System.ServiceProcess assembly it relies on won't even load under .NET 10. Only
|
||||
// offer the Service menu where it can actually work — on Win7/8 it simply isn't shown and no service
|
||||
// code is ever reached. (Mirrors how "capture individual apps" is gated to the versions that support
|
||||
// it.) The startup path is already load-safe via ServiceEntry; this keeps the menu load-safe too.
|
||||
if (OperatingSystem.IsWindowsVersionAtLeast(10, 0))
|
||||
menu.Items.Add(BuildServiceMenu());
|
||||
menu.Items.Add(optionsMenu);
|
||||
menu.Items.Add(helpMenu);
|
||||
return menu;
|
||||
@@ -2218,19 +2224,29 @@ public sealed class MainForm : Form
|
||||
});
|
||||
serviceMenu.DropDownOpening += (_, _) =>
|
||||
{
|
||||
var state = ServiceControl.Query();
|
||||
status.Text = "Service: " + DescribeServiceState(state);
|
||||
// Surface the running version + when it (re)started, so a self-update is visible at a glance.
|
||||
if (state is ServiceState.Running or ServiceState.Stopped && ServiceStore.LoadStatus() is { Version: { } ver } st)
|
||||
// Never let a status-query failure crash the menu (and with it the app). The menu only appears
|
||||
// on Win10+ where the service assembly loads fine, but a defensive net here is cheap insurance.
|
||||
try
|
||||
{
|
||||
status.Text += $" — version {ver}";
|
||||
if (state == ServiceState.Running && st.StartedUtc != default) status.Text += $", running since {DescribeAgo(st.StartedUtc)}";
|
||||
var state = ServiceControl.Query();
|
||||
status.Text = "Service: " + DescribeServiceState(state);
|
||||
// Surface the running version + when it (re)started, so a self-update is visible at a glance.
|
||||
if (state is ServiceState.Running or ServiceState.Stopped && ServiceStore.LoadStatus() is { Version: { } ver } st)
|
||||
{
|
||||
status.Text += $" — version {ver}";
|
||||
if (state == ServiceState.Running && st.StartedUtc != default) status.Text += $", running since {DescribeAgo(st.StartedUtc)}";
|
||||
}
|
||||
var installed = state != ServiceState.NotInstalled;
|
||||
install.Enabled = !installed;
|
||||
uninstall.Enabled = installed;
|
||||
start.Enabled = installed && state is ServiceState.Stopped;
|
||||
stop.Enabled = installed && state is ServiceState.Running;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
status.Text = "Service: status unavailable";
|
||||
logFile.Event($"service menu: status query failed {ex.GetType().Name}: {ex.Message}");
|
||||
}
|
||||
var installed = state != ServiceState.NotInstalled;
|
||||
install.Enabled = !installed;
|
||||
uninstall.Enabled = installed;
|
||||
start.Enabled = installed && state is ServiceState.Stopped;
|
||||
stop.Enabled = installed && state is ServiceState.Running;
|
||||
};
|
||||
return serviceMenu;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user