Windows Equivalent of dlsym

Hi all, I am trying to execute a function that gets linked from C file that gets linked into the executable in build.zig. I was able to get it to work for linux but i am not sure why the recommended way for windows is not working for me. Here is the code i am using and let me know what i am doing wrong please.

src/main.c

void add(int a, int b) {
  printf("Add a %d\n", a + b);
}

build.zig

const std = @import("std");

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

    const optimize = b.standardOptimizeOption(.{});

    const exe_mod = b.createModule(.{
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    const add = b.addSharedLibrary(.{
        .name = "add",
        .target = target,
        .optimize = optimize,
        .link_libc = true,
    });

    add.addCSourceFiles(.{ .root = b.path("src/"), .files = &.{
        "main.c",
    } });

    const exe = b.addExecutable(.{
        .name = "dynamic_dispatch",
        .root_module = exe_mod,
    });
    exe.linkLibC();
    exe.linkLibrary(add);

    b.installArtifact(exe);
    b.installArtifact(add);

    const run_cmd = b.addRunArtifact(exe);

    run_cmd.step.dependOn(b.getInstallStep());

    if (b.args) |args| {
        run_cmd.addArgs(args);
    }

    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);
}

and my src/main.zig

const Handle = if (builtin.os.tag == .windows) c.HMODULE else ?*anyopaque;
var main_program_handle: Handle = null;

pub fn initDynamicSymbolHandling() void {
    if (builtin.os.tag == .windows) {
        main_program_handle = c.GetModuleHandleA(null);
    } else {
        main_program_handle = c.dlopen(null, c.RTLD_LAZY);
    }

    if (main_program_handle == null) {
        std.log.err("No handle of the main program\n", .{});
    }
}

pub fn callCFunction() ?*const fn (c_int, c_int) callconv(.c) void {
    const method = "add";

    if (builtin.os.tag == .windows) {
        const proc = c.GetProcAddress(main_program_handle, method.ptr);
        if (proc == null) {
            std.log.err("No Proc .{any}\n", .{proc});
            return null;
        }

        return @ptrFromInt(@intFromPtr(proc));
    } else {
        const proc = c.dlsym(main_program_handle, method.ptr);
        std.debug.print("Proc is {any}\n", .{proc});
        return @ptrFromInt(@intFromPtr(c.dlsym(main_program_handle, method.ptr)));
    }
}

pub fn main() !void {
    initDynamicSymbolHandling();

    const add = callCFunction();

    if (add) |proc| {
        proc(1, 2);
    }

    std.debug.print("All your {s} are belong to us.\n", .{"codebase"});
}

const std = @import("std");
const builtin = @import("builtin");
const c = if (builtin.os.tag == .windows) @cImport({
    @cInclude("windows.h");
}) else @cImport({
    @cInclude("dlfcn.h");
});

Appreciate any kind of ur help.

I would also like to state that if i specify the dll file by name, (based on recommendations i find online) the execution works for windows too, but i do not like the fact that i have to specify the ‘dll’ file by its name.

  if (builtin.os.tag == .windows) {
            const libname = "add.dll";

            const handle = c.LoadLibraryA(libname.ptr);
            if (handle == null) {
                std.debug.print("Failed Loading DLL: {s}\n", .{libname});
                return null;
            }

            const symbol_name = "add";
            const add_fn_ptr = c.GetProcAddress(handle, symbol_name.ptr);

            if (add_fn_ptr == null) {
                std.debug.print("Symbol  {s}\n", .{symbol_name});
                return null;
            }

            return @ptrFromInt(@intFromPtr(add_fn_ptr));
        }
           

Thanks

Tbh I would be surprised that GetProcAddress() would work on executables on Windows, and the documentation also only mentions DLLs: GetProcAddress | Microsoft Learn

I am surprised that the POSIX API lets you open your own executable via dlopen() and lookup symbols via dlsym() though, and I would be even more surprised if this worked on a stripped release-mode executable which doesn’t contain any symbol information.

If you want to lookup a function address in a statically linked executable, just directly take its address like in this C snippet:

PS: ok after reading your build.zig more carefully: you are linking dynamically, I guess the difference is that calling dlsym() on an executable also looks through the dynamically linked libraries (which is surprising and new to me). I’m pretty sure that GetProcAddress() on Windows doesn’t do this, you’ll have to give it the handle of the DLL which exports the symbol, not of the exe loading that DLL.

PPS: I think your code should work if you just replace GetModuleHandle(null) with GetModuleHandle("add.dll") (since the DLL is already loaded).

Thanks. I just tried this and it doesnt work on linux when it gets compiled with ReleaseSmall too.

ps, replacing with GetModuleHandle(null) with GetModuleHandle(“add.dll”) didnt really fix the problem but i was able to get it work on windows with the code i posted at the end of my question.

Thanks.

For this to work reliably on Linux, you need -rdynamic (don’t recall what the field is called in std.Build).

1 Like