How to force library to build using /MD

Hi there!

We are gearing up to release a version of Tides of Revival, our open world RPG built in Zig. We would like to release a ReleaseSafe version if possible, but have hit a snag (or a few, but this is hopefully the last one)

We have a pretty uncommon setup. First, we are still on Zig 0.14.1 as it seems like it’s not worth the effort upgrading before the release. Second, we rely on a C++ library called The-Forge. We have generated bindings for this library, and are building a dll for it. This dll contains of a few static libraries - some The-Forge libraries (MSVC projects), and one that is our “binding library”.

Third, as implied, we are building with msvc as the target:
"command": "${workspaceFolder}/tools/binaries/zigup/zig build -Dztracy-enable=true -Dtarget=native-native-msvc --summary failures -Doptimize=ReleaseFast -freference-trace",

When we try to build, we get this error:

error: lld-link: /failifmismatch: mismatch detected for ‘RuntimeLibrary’:
note: C:\Projects\elvengroin-legacy.zig-cache\o\efaa0d8879bd7626baf1505d94068554\Log_glue.obj has value MT_StaticRelease
note: OS.lib(CameraController.obj) has value MD_DynamicRelease

Log_glue.cpp is part of the bindings, and here is how we set it up:

pub fn package(
    b: *std.Build,
    target: std.Build.ResolvedTarget,
    optimize: std.builtin.Mode,
    _: struct {},
) Package {
    const zforge = b.createModule(.{
        .target = target,
        .optimize = optimize,
        .root_source_file = b.path("external/The-Forge/main.zig"),
    });

    var zforge_cpp = b.addStaticLibrary(.{
        .name = "zforge",
        .target = target,
        .optimize = optimize,
    });

    zforge_cpp.linkLibC();
    // zforge_cpp.linkLibCpp();
    zforge_cpp.addIncludePath(b.path("external/The-Forge/Common_3/Application/Interfaces"));
    zforge_cpp.addIncludePath(b.path("external/The-Forge/Common_3/Graphics/Interfaces"));
    zforge_cpp.addIncludePath(b.path("external/The-Forge/Common_3/Resources/ResourceLoader/Interfaces"));
    zforge_cpp.addIncludePath(b.path("external/The-Forge/Common_3/Utilities/Interfaces"));
    zforge_cpp.addIncludePath(b.path("Common_3/Utilities/Log"));
    zforge_cpp.addCSourceFiles(.{
        .files = &.{
            "external/The-Forge/Common_3/Application/Interfaces/IFont_glue.cpp",
            "external/The-Forge/Common_3/Graphics/Interfaces/IGraphics_glue.cpp",
            "external/The-Forge/Common_3/Resources/ResourceLoader/Interfaces/IResourceLoader_glue.cpp",
            "external/The-Forge/Common_3/Utilities/Interfaces/IFileSystem_glue.cpp",
            "external/The-Forge/Common_3/Utilities/Interfaces/IMemory_glue.cpp",
            "external/The-Forge/Common_3/Utilities/Log/Log_glue.cpp",
        },
        .flags = &.{
            "-DTIDES",
            "-DNO_TIDES_FORGE_DEBUG",
            // "-MD",
        },
    });

As you can see we’ve tried to add “-MD” to the flags, however that doesn’t have any effect.

You can see the full build.zig for The-Forge here:

And the Tides build.zig here:

Any idea on how we could do this would be helpful.

Zig decides to link either to msvcrt.dll or to libcmt.lib depending on link_mode:

Log_glue build was MT_StaticRelease generated from zig link_mode = .static and OS.lib build was MD_DynamicRelease generated from zig link_mode = .dynamic.

You can view the link mode using the --show-builtin command line:

❯ zig build-exe -target native-windows-msvc -lc --show-builtin | grep link_mode
pub const link_mode: std.builtin.LinkMode = .static;
❯ zig build-exe -target native-windows-msvc -lc -static --show-builtin | grep link_mode
pub const link_mode: std.builtin.LinkMode = .static;
❯ zig build-exe -target native-windows-msvc -lc -dynamic --show-builtin | grep link_mode
pub const link_mode: std.builtin.LinkMode = .dynamic;

The default mode for exe is .static.

To set the link mode using build.zig set the options linkage field to the desired link_mode.
For the executable linkage is in the std.Build.ExecutableOptions of std.Build.addExecutable

1 Like

Hi, thanks for the reply!

I’m not sure if I misunderstood you, but if not, we had already tried that and unfortunately it didn’t make any difference:

    var zforge_cpp = b.addStaticLibrary(.{
        .name = "zforge",
        .target = target,
        .optimize = optimize,
    });
    zforge_cpp.linkage = .dynamic;

I’m not finding a similar option in StaticLibraryOptions nor in Module.CreateOptions.

I now tried adding it to the exe too and that didn’t help either :confused:

    const exe = b.addExecutable(.{
        .name = "TidesOfRevival",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
        .linkage = .dynamic,
    });

I have no idea if this will work, but judging by the /MD, /MT, /LD docs it could be that Zig doesn’t define certain macros. So in addition to setting exe.linkage = .dynamic you could try also doing the following:

zforge_cpp.root_module.addCMacro("_MT", "1");
zforge_cpp.root_module.addCMacro("_DLL", "1");
if (optimize == .Debug) {
    zforge_cpp.root_module.addCMacro("_DEBUG", "1");
}

I assume you want zforge_cpp to be a static library, so you don’t need to change zforge_cpp.linkage (it wouldn’t make any sense, the linkage field is what determines whether you build a .lib or .dll).

According to a random LLVM issue I found, the “mismatch detected” error comes from these lines. If the libraries you are linking with are compiled with _DLL defined, but your code isn’t, that would explain the problem.

3 Likes

Thank you! This worked :slight_smile:

I’m not entirely sure why we faced this now and not for Debug. Could it be that Zig uses the “Multi-threaded DLL” model for Debug but not for release builds?