Windows binaries: VCRUNTIME140.DLL not found (unless +crt-static)

I created a small Windows *.exe file with Rust and got this error report when it was executed on a Windows Server 2019:

Die Ausführung des Codes kann nicht fortgesetzt werden, da VCRUNTIME140.dll nicht gefunden wurde. Durch eine Neuinstallation des Programms kann das Problem möglicherweise behoben werden.

After some search, I figured creating a file .cargo/config.toml in my crate directory and adding the lines

[target.'cfg(all(windows, target_env = "msvc"))']
rustflags = ["-C", "target-feature=+crt-static"]

fixes my problem (after recompilation).

I don't understand what's the best practice here. Why isn't this option turned on by default (as Rust defaults to compiling static binaries, right?)?

I just found #71651, but I'm still a bit overwhelmed by all the information.

My question basically is: What to do to compile an *.exe file to be shipped as a standalone file? Should I add the above configuration option to projects that aim to run on Windows, or is there a downside?


P.S.: I also just found Static_vcruntime: Distribute Windows MSVC binaries without needing to deploy vcruntime.dll. Does this mean all my (future) Windows projects should do that?

If it's important to you to ship only an .exe file, then you should statically link the CRT.

The reason to not make this the default is because it is idiomatic to dynamically link the CRT when using MSVC. This will require your end users to install the vcredist package in order for your executable to run. With that said, the vcredist is one of those things that will tend to end up being installed sooner or later (often implicitly through other installation programs).

As for what the upsides and downsides are: If you statically link you're stuck with the CRT you linked into your binary. If you use vcredist, and it was properly installed, it can get security updates via Windows Update. Now I haven't actually checked to see how often the CRT libraries end up having vulnerabilities, so I'm not sure if this is a real-world problem.

Over the years (pre-Rust, because these issues exist when you use C/C++ as well) I've gone back and forth on what the best way to do it is. Nowadays I basically ask clients if they have vcredist already (which they almost always do). If not, would they be willing to install it as a prerequisite? If vcredist would be a problem I use static linking, in all other cases I use dynamic linking.

5 Likes

The C runtime is actually split into 2 parts on Windows, one component is shipped with the OS and updated when needed (MS call it the "Universal C Runtime"), and the other part is tied to a specific compiler version, and you can't really update that (outside of security issues) without recompiling the app (This is the vcruntime bit)

Dynamic linking is the "idiomatic" choice on Windows, it gives you the up-to-date C runtime via the OS, with the downside of having to ensure the user has the vcruntime component installed before you run your app (Which you'd normally do as part of the install process). Static linking on the other hand means you miss out on those security updates (Which might not matter for your app so :person_shrugging:t2:) and makes your app slightly larger because you're bundling code the OS is already providing.

That being said, the static_vcruntime crate in your post is probably what you want, it implements an approach that Microsoft devs call "hybrid linking", where you statically link the vcruntime component, while dynamically linking the universal C runtime component. You get the best of both worlds via that approach.

2 Likes

Thank you both for this information.

My current plan is to add this to my Cargo.toml:

[features]
# Enabling `windows-static` bundles `VCRUNTIME140.DLL` such that it's
# not necessary to install "Microsoft Visual C++ Redistributable".
# Use `cargo build --features=windows-static` to enable.
windows-static = ["dep:static_vcruntime"]

[target.'cfg(windows)'.build-dependencies]
static_vcruntime = { version = "2", optional = true }

And then in my build.rs:

fn main() -> std::io::Result<()> {
    // Bundle `VCRUNTIME140.DLL` on Windows:
    #[cfg(all(windows, feature = "windows-static"))]
    static_vcruntime::metabuild();
    Ok(())
}

Not that it seems that Visual Studio does actually support vcruntime binary updates too: https://learn.microsoft.com/en-us/cpp/porting/binary-compat-2015-2017?view=msvc-170

But this seems very cool, I have something that could use this.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.