Skip to content

hook: carry the MSVC runtime instead of asking for it - #544

Merged
mgth merged 1 commit into
masterfrom
hook-static-crt
Aug 16, 2026
Merged

hook: carry the MSVC runtime instead of asking for it#544
mgth merged 1 commit into
masterfrom
hook-static-crt

Conversation

@mgth

@mgth mgth commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Fixes #539.

The reporter reset his PC, reinstalled, and got a dialog naming both the culprit and the missing piece:

LittleBigMouse.Hook.exe - System Error — The code execution cannot proceed because VCRUNTIME140.dll was not found.

Rust's x86_64-pc-windows-msvc target links vcruntime140.dll dynamically, and Windows does not ship that DLL — only the UCRT is part of the OS. On a machine that never installed a VC++ redistributable, the daemon dies before main(). The UI starts, the daemon never does, and nothing in the app is in a position to explain why: from the user's side LBM simply "doesn't work".

Nothing in the tree ever provided the runtime — no crt-static, no .cargo/config.toml, and no redistributable in LittleBigMouse.iss. The C++ hook this one replaced did not have the problem, so the failure arrived with the port, and it lands on exactly the machines least equipped to diagnose it — clean installs.

Why static linking

Rather than bundling the redistributable in the installer:

  • the daemon is a standalone exe with no C dependency on Windowscargo tree --target x86_64-pc-windows-msvc -i cc prints nothing — so there is no second CRT to disagree with this one over a heap. The usual argument against a static CRT does not apply here.
  • it keeps the portable artifact working, which an installer-side fix would not.
  • one less thing to go wrong at install time on the machines already having a bad day.

Why the config is at the repository root

Cargo resolves .cargo/config.toml from the working directory upwards, never from the manifest, and CI builds the hook from the root with --manifest-path. Placed under LittleBigMouse-Hook-Rust/, the flag would be honoured for a developer building in that directory and silently dropped by the build that ships — the one failure mode the file exists to prevent. From the root, both are covered.

Verified rather than assumed, from the exact cwd CI uses:

$ cargo +nightly -Zunstable-options config get     # from the repository root
target.x86_64-pc-windows-msvc.rustflags = ["-C", "target-feature=+crt-static"]

The CI guard

Every machine that builds the hook has a redistributable installed, so this break is invisible right up to the user. The new step asserts on the artifact instead of trusting the flag: a PE names its imported DLLs in plain ASCII, so the check reads the file and needs no toolchain. It sits next to the existing VERSIONINFO assertion, which exists for the same reason.

Tested

  • cargo check --all-targets clean, cargo test green (81 tests, unchanged) — Linux
  • .cargo/config.toml parses, and resolves to the expected rustflags from both the root and the hook directory
  • workflow YAML parses, step ordering unchanged

Not tested — needs the Windows build

The MSVC target cannot be built or run from Linux. Still to confirm:

  • the hook compiles with the static CRT
  • the new guard passes, i.e. VCRUNTIME is gone from the imports
  • the daemon starts and hooks normally
  • ideally: it starts on a Windows with no redistributable installed — the machine this bug is actually about

The first two are covered by this PR's own CI run. The last one is the only real proof, and it needs a clean VM.

Scope

This covers the hook only. If the Avalonia native libraries shipped with the UI (libSkiaSharp.dll, av_libglesv2.dll) had the same need, the guard would not catch it — but the UI does start for the reporter, so they do not.

🤖 Generated with Claude Code

The Rust target links vcruntime140.dll dynamically, and Windows does not
ship it. On a machine that never installed a VC++ redistributable — a
fresh install, a reset PC — LittleBigMouse.Hook.exe dies before main()
with "VCRUNTIME140.dll was not found" (#539). The UI comes up, the daemon
never does, and nothing in the app is in a position to say why. The C++
hook this one replaced did not have the problem, so the whole failure
arrived with the port, on exactly the machines least able to diagnose it.

Static linking rather than a redistributable bundled in the installer:
the daemon is a standalone exe with no C dependency on Windows (`cargo
tree --target x86_64-pc-windows-msvc -i cc` finds nothing), so there is
no second CRT to disagree with this one over a heap — the reason static
linking is usually the wrong answer does not apply here. It also keeps
the portable artifact working, which an installer-side fix would not.

The config sits at the repository root rather than next to the hook's
Cargo.toml because Cargo resolves .cargo/config.toml from the working
directory upwards, never from the manifest, and CI builds the hook from
the root with --manifest-path. Under LittleBigMouse-Hook-Rust/ it would
be read by a developer building in that directory and ignored by the
build that ships.

CI asserts on the artifact rather than on the flag: every machine that
builds the hook has a redistributable installed, so the break is
invisible until it reaches a user who has none. A PE names its imported
DLLs in plain ASCII, so the check needs no toolchain.

Not verified here: the MSVC target cannot be built or run from Linux.
The Windows build still has to confirm that it compiles, that the guard
passes, and that the daemon starts — ideally on a Windows with no
redistributable, which is the machine the bug is about.

Co-Authored-By: Claude <noreply@anthropic.com>
@mgth mgth mentioned this pull request Aug 16, 2026
@mgth
mgth merged commit e0eb19a into master Aug 16, 2026
2 checks passed
@mgth
mgth deleted the hook-static-crt branch August 16, 2026 14:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

System error

1 participant