MSVC linking woes

I am on an x86_64-windows machine and I am trying to target x86_64-windows-msvc.

My project downloads some DLLs and tries to link against them using the zig build system.
The DLLs target the MSVC ABI.

However, when I run

zig build -Dtarget=native-native-msvc

I get linker errors for lots of undefined symbols:

error: lld-link: undefined symbol: __declspec(dllimport) closesocket
    note: referenced by zenohc.lib(zenohc.zenohc.87d05d4354ee0cf2-cgu.0.rcgu.o):(zenoh_runtime::ZRuntime::block_in_place::_$u7b$$u7b$closure$u7d$$u7d$::hee46949cbf4edbe3)

error: lld-link: undefined symbol: __declspec(dllimport) setsockopt
    note: referenced by zenohc.lib(zenohc.zenohc.87d05d4354ee0cf2-cgu.0.rcgu.o):(zenoh::net::runtime::orchestrator::_$LT$impl$u20$zenoh..net..runtime..Runtime$GT$::start_scout::_$u7b$$u7b$closure$u7d$$u7d$::h5deafa0657b41f30)
    note: referenced by zenohc.lib(zenohc.zenohc.87d05d4354ee0cf2-cgu.0.rcgu.o):(zenoh::net::runtime::orchestrator::_$LT$impl$u20$zenoh..net..runtime..Runtime$GT$::start_scout::_$u7b$$u7b$closure$u7d$$u7d$::h5deafa0657b41f30)
    note: referenced by zenohc.lib(zenohc.zenohc.87d05d4354ee0cf2-cgu.0.rcgu.o):(zenoh::net::runtime::orchestrator::_$LT$impl$u20$zenoh..net..runtime..Runtime$GT$::start_scout::_$u7b$$u7b$closure$u7d$$u7d$::h5deafa0657b41f30)
    note: referenced 25 more times

I’m not sure why I am getting linker errors. I am linking libc in my build.zig:

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // expose a public module called "zenoh"
    const zenoh = b.addModule("zenoh", .{
        .root_source_file = b.path("src/root.zig"),
        .target = target,
        .optimize = optimize,
    });

    // download the correct pre-compiled zenoh static library
    const zenoh_c_dep = switch (target.result.cpu.arch) {
        .x86_64 => switch (target.result.os.tag) {
            .windows => switch (target.result.abi) {
                .gnu => b.lazyDependency("zenoh_c_x86_64_windows_gnu", .{}),
                .msvc => b.lazyDependency("zenoh_c_x86_64_windows_msvc", .{}),
                else => @panic("unsupported target"),
            },
            .linux => switch (target.result.abi) {
                .musl => b.lazyDependency("zenoh_c_x86_64_linux_musl", .{}),
                .gnu => b.lazyDependency("zenoh_c_x86_64_linux_gnu", .{}),
                else => @panic("unsupported target"),
            },
            .macos => switch (target.result.abi) {
                .none => b.lazyDependency("zenoh_c_x86_64_macos_none", .{}),
                else => @panic("unsupported target"),
            },
            else => @panic("unsupported target"),
        },
        .aarch64 => switch (target.result.os.tag) {
            .linux => switch (target.result.abi) {
                .musl => b.lazyDependency("zenoh_c_aarch64_linux_musl", .{}),
                .gnu => b.lazyDependency("zenoh_c_aarch64_linux_gnu", .{}),
                else => @panic("unsupported target"),
            },
            .macos => switch (target.result.abi) {
                .none => b.lazyDependency("zenoh_c_aarch64_macos_none", .{}),
                else => @panic("unsupported target"),
            },
            else => @panic("unsupported target"),
        },
        else => @panic("unsupported target"),
    } orelse return;

    const zenoh_c_static_lib_path = switch (target.result.os.tag) {
        .linux, .macos => zenoh_c_dep.path("lib/libzenohc.a"),
        .windows => switch (target.result.abi) {
            .msvc => zenoh_c_dep.path("lib/zenohc.lib"),
            .gnu => zenoh_c_dep.path("lib/libzenohc.a"),
            else => @panic("unsupported target"),
        },
        else => @panic("unsupported target"),
    };
    // expose the functions in the header to zig using translate-c
    const translate_c = b.addTranslateC(.{
        .link_libc = true,
        .optimize = optimize,
        .target = target,
        .root_source_file = zenoh_c_dep.path("include/zenoh.h"),
    });
    zenoh.addImport("zenoh_c", translate_c.createModule());

    // link the zenoh static library to zig
    zenoh.addObjectFile(zenoh_c_static_lib_path);

    // run some unit tests to sanity check
    const lib_unit_tests = b.addTest(.{
        .root_module = zenoh,
    });
    lib_unit_tests.linkLibC();
    const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
    const test_step = b.step("test", "Run unit tests");
    test_step.dependOn(&run_lib_unit_tests.step);

    // examples
    const examples_tests = b.addTest(.{
        .root_source_file = b.path("examples/root.zig"),
        .target = target,
        .optimize = optimize,
    });
    examples_tests.linkLibC();
    examples_tests.root_module.addImport("zenoh", zenoh);
    const run_examples_tests = b.addRunArtifact(examples_tests);
    const examples_step = b.step("examples", "Run the examples.");
    examples_step.dependOn(&run_examples_tests.step);

    // make the default step run the tests
    b.default_step.dependOn(test_step);
}

What am I doing wrong here? Do I need to install visual studio or something?

This is the output of zig libc:

zig libc
# The directory that contains `stdlib.h`.
# On POSIX-like systems, include directories be found with: `cc -E -Wp,-v -xc /dev/null`
include_dir=C:\Program Files (x86)\Windows Kits\10\Include\10.0.22621.0\ucrt

# The system-specific include directory. May be the same as `include_dir`.
# On Windows it's the directory that includes `vcruntime.h`.
# On POSIX it's the directory that includes `sys/errno.h`.
sys_include_dir=C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC\14.39.33519\include

# The directory that contains `crt1.o` or `crt2.o`.
# On POSIX, can be found with `cc -print-file-name=crt1.o`.
# Not needed when targeting MacOS.
crt_dir=C:\Program Files (x86)\Windows Kits\10\Lib\10.0.22621.0\ucrt\x64

# The directory that contains `vcruntime.lib`.
# Only needed when targeting MSVC on Windows.
msvc_lib_dir=C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC\14.39.33519\Lib\x64

# The directory that contains `kernel32.lib`.
# Only needed when targeting MSVC on Windows.
kernel32_lib_dir=C:\Program Files (x86)\Windows Kits\10\Lib\10.0.22621.0\um\x64

# The directory that contains `crtbeginS.o` and `crtendS.o`
# Only needed when targeting Haiku.
gcc_dir=

If you were calling “closesocket” from zig, you’d call an extern function that declares it comes from the “ws2_32” library (see ws2_32.zig). Since C doesn’t have a mechanism to tie functions to the library they come from, you have to manually specify these libraries yourself.

So in general, there are many functions provided by builtin DLL’s on Windows, like user32, kernel32, ws2_32, you learn which ones over time but when you get linker errors you can usually just try googling those functions to see if they happen to be one of these. Here’s the entry for closesocket: closesocket function (winsock.h) - Win32 apps | Microsoft Learn
You can see at the bottom it comes from ws2_32 so in your build.zig you’ll want to add a call to linkSystemLibrary("ws2_32") if your target is windows.

P.S. there is mechanism C code can use to add libraries to be linked via pragma comment lib…so sometimes libraries use this to encode library dependencies in their source code. However I don’t think every toolchain supports these, Zig being one of that that doesn’t support it.

1 Like

Thanks!!! Using these tips I was able to search for the missing functions and link the system libraries:

lib_unit_tests.linkSystemLibrary("ws2_32");
lib_unit_tests.linkSystemLibrary("Iphlpapi");
lib_unit_tests.linkSystemLibrary("Advapi32");
lib_unit_tests.linkSystemLibrary("Bcrypt");
1 Like

If I went through the effort of making my one msvc dependency compile to gnu (mingw), would this make my life easier here?

Edit: maybe, I would have to rebuild npcap for mingw.

Yes. GNU is a “hermetic toolchain” vs MSVC which is not. Meaning, GNU has zero dependencies, if you have zig, you have the GNU toolchain, and it’s going to work regardless of whether you’re compiling from windows/linux/macOS/etc.

MSVC on the other hand requires the MSVC toolchain, which is a tough dependency to have. I would also say there are different levels of dependency on MSVC that will vary depending on the project. Some C projects will only require the MSVC headers/libraries, but others will require MSVC compiler/toolchain as well. If it’s only the former, then you could effectively make the MSVC toolchain hermetic by packaging up the headers/libraries and just adding those as a normal zig dependency. This should even allow you to continue building from other operating systems like linux/macOS. However, if the project is using MSVC-specific compiler/linker features, then you’ll either need to find a way to get the MSVC toolchain running on the build machine (which is a pre-compiled win32 COFF executable only intended to run on Windows) or update the project to remove the dependence on that toolchain.

Maybe one day I’ll get around to making it easier to build MSVC projects with Zig but I haven’t done so yet. But even if I have done so, it’s probably not a bad idea to update projects to work without MSVC anyway.

3 Likes