Static_vcruntime: Distribute Windows MSVC binaries without needing to deploy vcruntime.dll

EDIT: Version 2 has been released. Now 50% less hacky.

static_vcruntime

When you build a Windows application using the MSVC toolchain, by default it will produce a .exe that dynamically links to vcruntime140.dll. This dll will then need to be deployed when distributing the application. The static_vcruntime crate makes it so when you build your application in release mode, the vcruntime is statically linked instead.

Usage

Add it as a build dependency:

[build-dependencies]
static_vcruntime = "2.0"

Then put this in your build.rs:

fn main() {
    static_vcruntime::metabuild();
}

That is all. Then when you build in release mode the vcruntime library will be statically linked instead of dynamically:

cargo build --release

FAQ

How is this different to +crt-static?

Using +crt-static does statically link the vcruntime but it also statically links the universal C runtime (ucrt). This is entirely unnecessary because the ucrt is included with Windows already.

Can I do this manually instead?

You can! But it requires making a special configuration file. To do so create a folder in your project called .cargo. In that folder make a file called config.toml. So the directory structure is like this:

.cargo
|    config.toml

Then copy this into config.toml:

# Statically link the vcruntime.
# This should go in `.cargo/config.toml`. It will not work in `cargo.toml`.
[target.'cfg(all(windows, target_env = "msvc"))']
rustflags = [
	"-C", "target-feature=+crt-static"
	"-C", "link-args=/DEFAULTLIB:ucrt.lib /DEFAULTLIB:libvcruntime.lib libcmt.lib",
	"-C", "link-args=/NODEFAULTLIB:libvcruntimed.lib /NODEFAULTLIB:vcruntime.lib /NODEFAULTLIB:vcruntimed.lib"
	"-C", "link-args=/NODEFAULTLIB:libcmtd.lib /NODEFAULTLIB:msvcrt.lib /NODEFAULTLIB:msvcrtd.lib"
	"-C", "link-args=/NODEFAULTLIB:libucrt.lib /NODEFAULTLIB:libucrtd.lib /NODEFAULTLIB:ucrtd.lib"
]

One issue with doing it manually is there's no way I know of to only apply it to release versions other than to abuse the way cargo looks for configuration files.


Alternatives

Instead of statically linking you can deploy the vcruntime in one of two ways. In either case, from the start menu open "Developer Powershell". Then type:

cd $env:VCToolsRedistDir

This will put you in the directory with redistributable files. You can then either redistribute the installers found there (typically either vcredist_x64.exe or vcredist_x86.exe) or you can only distribute the dll itself. The dll is found in the following sub-directories:

x64\Microsoft.VC142.CRT\vcruntime140.dll

Or:

x86\Microsoft.VC142.CRT\vcruntime140.dll
7 Likes

This is awesome -- thanks for putting this together.

Nice job :slightly_smiling_face:

One thing though: you mention the behavior only being enabled when on --release, but the truth is that your cfg is currently based off debug-assertions = false, so while it will work with default profile settings, it may not always match --release.

If you do want to match --release, the trick is to use a build.rs script:

//! build.rs

fn main ()
{
    println!("cargo:rerun-if-env-changed=PROFILE");
    println!("cargo:rerun-if-changed=build.rs"); // Optional
    if ::std::env::var("PROFILE").map_or(false, |s| s == "release") {
        println!("cargo:rustc-cfg=release");
    }
}

which will enable you to use cfg(release) in your lib.rs.

3 Likes

Thank you! I wasn't aware of this trick. I've updated the build script as you suggest and uploaded version 1.1.

2 Likes

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.