DynLib no longer supports Windows, and what is the intended solution?

After uploaded 0.16.0, based on the release notes, I have found that DynLib no longer supports Windows, yet the only issues I could find is this one from the codeberg. Although someone has proposed a workaround, I had a couple of questions come in mind:

  • Is the change to DynLib temporary? Will it be removed or it will stay at this state of excluding windows?
  • given the release note: DynLib: removed Windows support. Now users must use LoadLibraryExW and GetProcAddress directly, which is probably what they were already doing anyway. Since I don’t seem to find much info related to the alternative solutions besides the one from the codeberg, may I know anyone actually doing this and managed to get it work? I wish to see a couple more examples before I update my library that needs to use DynLib.
1 Like

Hey, I had the exact same issue and I ended up with the following code:

const std = @import("std");
const Allocator = std.mem.Allocator;
const DynLib = std.DynLib;
const builtin = @import("builtin");
const oom = @import("misc").oom;

const HMODULE = *anyopaque;
const FARPROC = *anyopaque;

extern "kernel32" fn LoadLibraryExW(
    lpLibFileName: [*:0]const u16,
    hFile: ?*anyopaque,
    dwFlags: u32,
) callconv(.winapi) ?HMODULE;

extern "kernel32" fn FreeLibrary(hLibModule: HMODULE) callconv(.winapi) i32;
extern "kernel32" fn GetProcAddress(hModule: HMODULE, lpProcName: [*:0]const u8) callconv(.winapi) ?FARPROC;
extern "kernel32" fn GetLastError() callconv(.winapi) u32;

lib: Lib,

const os = builtin.os.tag;
const LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x1000;

const Self = @This();
const Lib = if (os == .windows) HMODULE else DynLib;
const Error = error{ UnsupportedOS, LoadFailed };

pub fn open(alloc: Allocator, path: []const u8, name: []const u8) Error!Self {
    const full_path = std.fmt.allocPrint(alloc, "{s}{s}{s}", .{
        path,
        std.Io.Dir.path.sep_str,
        try libName(alloc, name),
    }) catch oom();

    const lib_handle = switch (os) {
        .windows => win: {
            var path_utf16: [std.fs.max_path_bytes:0]u16 = undefined;
            const n = std.unicode.utf8ToUtf16Le(&path_utf16, full_path) catch return error.LoadFailed;
            path_utf16[n] = 0;

            break :win LoadLibraryExW(
                @ptrCast(path_utf16[0..n].ptr),
                null,
                LOAD_LIBRARY_SEARCH_DEFAULT_DIRS,
            ) orelse return error.LoadFailed;
        },
        else => DynLib.open(full_path) catch return error.LoadFailed,
    };

    return .{
        .lib = lib_handle,
    };
}

fn libName(alloc: Allocator, name: []const u8) Error![]const u8 {
    return switch (os) {
        .linux => std.fmt.allocPrint(alloc, "lib{s}.so", .{name}) catch oom(),
        .macos => std.fmt.allocPrint(alloc, "lib{s}.dylib", .{name}) catch oom(),
        .windows => std.fmt.allocPrint(alloc, "{s}.dll", .{name}) catch oom(),
        else => error.UnsupportedOS,
    };
}

pub fn lookup(self: *Self, T: type, name: [:0]const u8) ?T {
    return switch (os) {
        .windows => @ptrCast(GetProcAddress(self.lib, name.ptr) orelse return null),
        else => self.lib.lookup(T, name),
    };
}

pub fn close(self: *Self) void {
    switch (os) {
        .windows => _ = FreeLibrary(self.lib),
        else => self.lib.close(),
    }
}

It works on windows, linux and macos for my use cases. Hope this helps!

3 Likes

Thank you very much, and this version looks cleaner! I will further evaluate both versions to see if I could get my dynamic libraries back to the working condition.

1 Like