From 2d4d47ed82bbfb560f8a7fe42c7aa043d8ebf90b Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Thu, 23 Apr 2026 00:42:23 +0900 Subject: [PATCH 1/2] windows: provide DllMain stub for non-MSVC ABI Part of preparation for upstreaming a Win32 application runtime (see discussion #2563). This is one of three small build-related fixes that unblock the Windows GNU-ABI library build. When targeting Windows with GNU ABI, the existing `DllMain` declaration falls through to `void` (a type), which Zig stdlib's `start.zig` then attempts to call as a function via `root.DllMain(...)` - producing the compile error "type 'type' not a function". Restructure the conditional so that: - non-Windows builds keep `DllMain = void` - Windows + MSVC keeps the existing CRT-init handler (unchanged) - Windows + non-MSVC gets a no-op `BOOL` handler This unblocks `zig build -Dtarget=native-native-gnu -Dapp-runtime=none` on Windows. --- src/main_c.zig | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main_c.zig b/src/main_c.zig index ef8d9ec7e..0ded209f6 100644 --- a/src/main_c.zig +++ b/src/main_c.zig @@ -168,8 +168,7 @@ pub export fn ghostty_string_free(str: String) void { // but not MSVC. No upstream issue tracks this exact gap as of 2026-03-26. // Closest: Codeberg ziglang/zig #30936 (reimplement crt0 code). // Remove this DllMain when Zig handles MSVC DLL CRT init natively. -pub const DllMain = if (builtin.os.tag == .windows and - builtin.abi == .msvc) struct { +pub const DllMain = if (builtin.os.tag != .windows) void else if (builtin.abi == .msvc) struct { const BOOL = std.os.windows.BOOL; const HINSTANCE = std.os.windows.HINSTANCE; const DWORD = std.os.windows.DWORD; @@ -200,7 +199,19 @@ pub const DllMain = if (builtin.os.tag == .windows and else => return TRUE, } } -}.handler else void; +}.handler else struct { + // GNU ABI: provide a no-op DllMain so Zig's start.zig doesn't + // try to call a type instead of a function. + const BOOL = std.os.windows.BOOL; + const HINSTANCE = std.os.windows.HINSTANCE; + const DWORD = std.os.windows.DWORD; + const LPVOID = std.os.windows.LPVOID; + const TRUE = std.os.windows.TRUE; + + pub fn handler(_: HINSTANCE, _: DWORD, _: LPVOID) callconv(.winapi) BOOL { + return TRUE; + } +}.handler; test "ghostty_string_s empty string" { const testing = std.testing; From 5a84afef29e2b46cb20db78278f46926bcc61a5d Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Thu, 23 Apr 2026 02:17:04 +0900 Subject: [PATCH 2/2] address review: collapse DllMain into a single struct Per review feedback (#12373), fold the nested `if/else if/else` into a single Windows-gated struct whose handler picks up the abi difference via a comptime check. This removes the duplicated `const BOOL = ...` block that the two per-abi structs shared. --- src/main_c.zig | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/main_c.zig b/src/main_c.zig index 0ded209f6..f61565f26 100644 --- a/src/main_c.zig +++ b/src/main_c.zig @@ -161,14 +161,17 @@ pub export fn ghostty_string_free(str: String) void { // function that depends on CRT internal state (setlocale, malloc from C // dependencies, C++ constructors in glslang) crashes with null pointer // dereferences. Declaring DllMain causes Zig's start.zig to call it -// during DLL_PROCESS_ATTACH/DETACH, and we forward to the CRT bootstrap -// functions from libvcruntime and libucrt (already linked). +// during DLL_PROCESS_ATTACH/DETACH, and for MSVC we forward to the CRT +// bootstrap functions from libvcruntime and libucrt (already linked). +// For other ABIs (MinGW) the handler is a no-op since dllcrt2.obj already +// handles CRT init; we still need `DllMain` declared so that Zig's +// start.zig does not fall back to calling a non-function value. // // This is a workaround. Zig handles MinGW DLLs correctly (via dllcrt2.obj) // but not MSVC. No upstream issue tracks this exact gap as of 2026-03-26. // Closest: Codeberg ziglang/zig #30936 (reimplement crt0 code). // Remove this DllMain when Zig handles MSVC DLL CRT init natively. -pub const DllMain = if (builtin.os.tag != .windows) void else if (builtin.abi == .msvc) struct { +pub const DllMain = if (builtin.os.tag == .windows) struct { const BOOL = std.os.windows.BOOL; const HINSTANCE = std.os.windows.HINSTANCE; const DWORD = std.os.windows.DWORD; @@ -185,6 +188,8 @@ pub const DllMain = if (builtin.os.tag != .windows) void else if (builtin.abi == const __acrt_uninitialize = @extern(*const fn (c_int) callconv(.c) c_int, .{ .name = "__acrt_uninitialize" }); pub fn handler(_: HINSTANCE, fdwReason: DWORD, _: LPVOID) callconv(.winapi) BOOL { + // Only MSVC needs to bootstrap the CRT; MinGW handles it via dllcrt2.obj. + if (builtin.abi != .msvc) return TRUE; switch (fdwReason) { DLL_PROCESS_ATTACH => { if (__vcrt_initialize() < 0) return FALSE; @@ -199,19 +204,7 @@ pub const DllMain = if (builtin.os.tag != .windows) void else if (builtin.abi == else => return TRUE, } } -}.handler else struct { - // GNU ABI: provide a no-op DllMain so Zig's start.zig doesn't - // try to call a type instead of a function. - const BOOL = std.os.windows.BOOL; - const HINSTANCE = std.os.windows.HINSTANCE; - const DWORD = std.os.windows.DWORD; - const LPVOID = std.os.windows.LPVOID; - const TRUE = std.os.windows.TRUE; - - pub fn handler(_: HINSTANCE, _: DWORD, _: LPVOID) callconv(.winapi) BOOL { - return TRUE; - } -}.handler; +}.handler else void; test "ghostty_string_s empty string" { const testing = std.testing;