From 5aeeb6a0a496b962638fdfd6d20d289e27f10112 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 1 May 2026 17:07:18 -0700 Subject: [PATCH 1/3] Conditionally export wrapper around Zig-defined DllMain function when linking libc This logic exists for other main functions, so it makes sense to do it for DllMain, too. --- lib/std/start.zig | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/std/start.zig b/lib/std/start.zig index 52de24257f..4d170c1704 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -23,6 +23,10 @@ comptime { const dll_main_crt_startup = if (builtin.abi.isGnu()) "DllMainCRTStartup" else "_DllMainCRTStartup"; if (native_os == .windows and !builtin.link_libc and !@hasDecl(root, dll_main_crt_startup)) { @export(&DllMainCRTStartup, .{ .name = dll_main_crt_startup }); + } else if (native_os == .windows and builtin.link_libc and @hasDecl(root, "DllMain")) { + if (!@typeInfo(@TypeOf(root.DllMain)).@"fn".calling_convention.eql(.winapi)) { + @export(&DllMain, .{ .name = "DllMain" }); + } } } else if (builtin.output_mode == .Exe or @hasDecl(root, "main")) { if (builtin.link_libc and @hasDecl(root, "main")) { @@ -88,6 +92,14 @@ fn DllMainCRTStartup( return .TRUE; } +fn DllMain( + hinstDLL: std.os.windows.HINSTANCE, + fdwReason: std.os.windows.DWORD, + lpReserved: std.os.windows.LPVOID, +) callconv(.winapi) std.os.windows.BOOL { + return root.DllMain(hinstDLL, fdwReason, lpReserved); +} + fn wasm_freestanding_start() callconv(.c) void { // This is marked inline because for some reason LLVM in // release mode fails to inline it, and we want fewer call frames in stack traces. From 8b16160b6ffeb11048be32402841bf2f2ef07a16 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 1 May 2026 17:14:10 -0700 Subject: [PATCH 2/3] DllMain symbols: use ptrCast to allow using non-std pointer type `HINSTANCE` is `*opaque {}` so using a type that should be compatible from alternate bindings (zigwin32, for example) would be a (false positive) compile error without this ptrCast. --- lib/std/start.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/start.zig b/lib/std/start.zig index 4d170c1704..1387f0385d 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -86,7 +86,7 @@ fn DllMainCRTStartup( } if (@hasDecl(root, "DllMain")) { - return root.DllMain(hinstDLL, fdwReason, lpReserved); + return root.DllMain(@ptrCast(hinstDLL), fdwReason, lpReserved); } return .TRUE; @@ -97,7 +97,7 @@ fn DllMain( fdwReason: std.os.windows.DWORD, lpReserved: std.os.windows.LPVOID, ) callconv(.winapi) std.os.windows.BOOL { - return root.DllMain(hinstDLL, fdwReason, lpReserved); + return root.DllMain(@ptrCast(hinstDLL), fdwReason, lpReserved); } fn wasm_freestanding_start() callconv(.c) void { From a450016eb1816a8f0989626dde4b0c1865cba33d Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 1 May 2026 19:24:59 -0700 Subject: [PATCH 3/3] Add tests for DllMain to windows_entry_points standalone test --- .../standalone/windows_entry_points/build.zig | 71 +++++++++++++++++++ .../standalone/windows_entry_points/dllmain.c | 9 +++ .../windows_entry_points/dllmain.zig | 18 +++++ .../windows_entry_points/loaddll.zig | 13 ++++ 4 files changed, 111 insertions(+) create mode 100644 test/standalone/windows_entry_points/dllmain.c create mode 100644 test/standalone/windows_entry_points/dllmain.zig create mode 100644 test/standalone/windows_entry_points/loaddll.zig diff --git a/test/standalone/windows_entry_points/build.zig b/test/standalone/windows_entry_points/build.zig index 236ebe95c6..d63de59c2b 100644 --- a/test/standalone/windows_entry_points/build.zig +++ b/test/standalone/windows_entry_points/build.zig @@ -139,4 +139,75 @@ pub fn build(b: *std.Build) void { _ = exe.getEmittedBin(); test_step.dependOn(&exe.step); } + + const load_dll_exe = b.addExecutable(.{ .name = "load_dll", .root_module = b.createModule(.{ + .root_source_file = b.path("loaddll.zig"), + .target = target, + .optimize = .Debug, + }) }); + + { + const dll = b.addLibrary(.{ + .name = "zig_dllmain", + .linkage = .dynamic, + .root_module = b.createModule(.{ + .root_source_file = b.path("dllmain.zig"), + .target = target, + .optimize = .Debug, + }), + }); + + const run = b.addRunArtifact(load_dll_exe); + run.addArtifactArg(dll); + run.expectStdErrEqual("hello from DllMain"); + run.expectStdOutEqual(""); + run.expectExitCode(0); + run.skip_foreign_checks = true; + + test_step.dependOn(&run.step); + } + + { + const dll = b.addLibrary(.{ + .name = "zig_dllmain_link_libc", + .linkage = .dynamic, + .root_module = b.createModule(.{ + .root_source_file = b.path("dllmain.zig"), + .target = target, + .optimize = .Debug, + .link_libc = true, + }), + }); + + const run = b.addRunArtifact(load_dll_exe); + run.addArtifactArg(dll); + run.expectStdErrEqual("hello from DllMain"); + run.expectStdOutEqual(""); + run.expectExitCode(0); + run.skip_foreign_checks = true; + + test_step.dependOn(&run.step); + } + + { + const dll = b.addLibrary(.{ + .name = "c_dllmain", + .linkage = .dynamic, + .root_module = b.createModule(.{ + .target = target, + .optimize = .Debug, + .link_libc = true, + }), + }); + dll.root_module.addCSourceFile(.{ .file = b.path("dllmain.c") }); + + const run = b.addRunArtifact(load_dll_exe); + run.addArtifactArg(dll); + run.expectStdErrEqual("hello from DllMain"); + run.expectStdOutEqual(""); + run.expectExitCode(0); + run.skip_foreign_checks = true; + + test_step.dependOn(&run.step); + } } diff --git a/test/standalone/windows_entry_points/dllmain.c b/test/standalone/windows_entry_points/dllmain.c new file mode 100644 index 0000000000..266ce5876b --- /dev/null +++ b/test/standalone/windows_entry_points/dllmain.c @@ -0,0 +1,9 @@ +#include +#include + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { + if (fdwReason == DLL_PROCESS_ATTACH) { + fprintf(stderr, "hello from DllMain"); + } + return TRUE; +} diff --git a/test/standalone/windows_entry_points/dllmain.zig b/test/standalone/windows_entry_points/dllmain.zig new file mode 100644 index 0000000000..f4df3a5afb --- /dev/null +++ b/test/standalone/windows_entry_points/dllmain.zig @@ -0,0 +1,18 @@ +const std = @import("std"); +const windows = std.os.windows; + +const DLL_PROCESS_ATTACH = 1; + +pub fn DllMain( + hinstDLL: windows.HINSTANCE, + fdwReason: windows.DWORD, + lpReserved: windows.LPVOID, +) windows.BOOL { + _ = hinstDLL; + _ = lpReserved; + switch (fdwReason) { + DLL_PROCESS_ATTACH => std.debug.print("hello from DllMain", .{}), + else => {}, + } + return .TRUE; +} diff --git a/test/standalone/windows_entry_points/loaddll.zig b/test/standalone/windows_entry_points/loaddll.zig new file mode 100644 index 0000000000..116fe36a64 --- /dev/null +++ b/test/standalone/windows_entry_points/loaddll.zig @@ -0,0 +1,13 @@ +const std = @import("std"); +const windows = std.os.windows; + +extern "kernel32" fn LoadLibraryW(windows.LPCWSTR) callconv(.winapi) ?windows.HMODULE; + +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const args = try init.minimal.args.toSlice(arena); + if (args.len < 2) return error.NoDllPathSpecified; + const dll_path = args[1]; + const dll_path_w = try std.unicode.wtf8ToWtf16LeAllocZ(arena, dll_path); + _ = LoadLibraryW(dll_path_w) orelse return error.FailedToLoadDll; +}