mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-05 23:28:21 +00:00
windows: simplify DLL init test and improve README
This commit is contained in:
committed by
Mitchell Hashimoto
parent
5d92222621
commit
ca08ab8619
@@ -17,53 +17,6 @@ const state = &@import("global.zig").state;
|
||||
const apprt = @import("apprt.zig");
|
||||
const internal_os = @import("os/main.zig");
|
||||
|
||||
// On Windows, Zig's _DllMainCRTStartup does not initialize the MSVC C
|
||||
// runtime when targeting MSVC ABI. Without initialization, any C library
|
||||
// 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).
|
||||
//
|
||||
// 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 and
|
||||
builtin.abi == .msvc) struct
|
||||
{
|
||||
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;
|
||||
const FALSE = std.os.windows.FALSE;
|
||||
|
||||
const DLL_PROCESS_ATTACH: DWORD = 1;
|
||||
const DLL_PROCESS_DETACH: DWORD = 0;
|
||||
|
||||
const __vcrt_initialize = @extern(*const fn () callconv(.c) c_int, .{ .name = "__vcrt_initialize" });
|
||||
const __vcrt_uninitialize = @extern(*const fn (c_int) callconv(.c) c_int, .{ .name = "__vcrt_uninitialize" });
|
||||
const __acrt_initialize = @extern(*const fn () callconv(.c) c_int, .{ .name = "__acrt_initialize" });
|
||||
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 {
|
||||
switch (fdwReason) {
|
||||
DLL_PROCESS_ATTACH => {
|
||||
if (__vcrt_initialize() < 0) return FALSE;
|
||||
if (__acrt_initialize() < 0) return FALSE;
|
||||
return TRUE;
|
||||
},
|
||||
DLL_PROCESS_DETACH => {
|
||||
_ = __acrt_uninitialize(1);
|
||||
_ = __vcrt_uninitialize(1);
|
||||
return TRUE;
|
||||
},
|
||||
else => return TRUE,
|
||||
}
|
||||
}
|
||||
}.handler else void;
|
||||
|
||||
// Some comptime assertions that our C API depends on.
|
||||
comptime {
|
||||
// We allow tests to reference this file because we unit test
|
||||
@@ -203,6 +156,52 @@ pub export fn ghostty_string_free(str: String) void {
|
||||
str.deinit();
|
||||
}
|
||||
|
||||
// On Windows, Zig's _DllMainCRTStartup does not initialize the MSVC C
|
||||
// runtime when targeting MSVC ABI. Without initialization, any C library
|
||||
// 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).
|
||||
//
|
||||
// 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 and
|
||||
builtin.abi == .msvc) struct {
|
||||
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;
|
||||
const FALSE = std.os.windows.FALSE;
|
||||
|
||||
const DLL_PROCESS_ATTACH: DWORD = 1;
|
||||
const DLL_PROCESS_DETACH: DWORD = 0;
|
||||
|
||||
const __vcrt_initialize = @extern(*const fn () callconv(.c) c_int, .{ .name = "__vcrt_initialize" });
|
||||
const __vcrt_uninitialize = @extern(*const fn (c_int) callconv(.c) c_int, .{ .name = "__vcrt_uninitialize" });
|
||||
const __acrt_initialize = @extern(*const fn () callconv(.c) c_int, .{ .name = "__acrt_initialize" });
|
||||
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 {
|
||||
switch (fdwReason) {
|
||||
DLL_PROCESS_ATTACH => {
|
||||
if (__vcrt_initialize() < 0) return FALSE;
|
||||
if (__acrt_initialize() < 0) return FALSE;
|
||||
return TRUE;
|
||||
},
|
||||
DLL_PROCESS_DETACH => {
|
||||
_ = __acrt_uninitialize(1);
|
||||
_ = __vcrt_uninitialize(1);
|
||||
return TRUE;
|
||||
},
|
||||
else => return TRUE,
|
||||
}
|
||||
}
|
||||
}.handler else void;
|
||||
|
||||
test "ghostty_string_s empty string" {
|
||||
const testing = std.testing;
|
||||
const empty_string = String.empty;
|
||||
|
||||
3
test/windows/.gitignore
vendored
Normal file
3
test/windows/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*.exe
|
||||
*.pdb
|
||||
*.dll
|
||||
@@ -10,12 +10,17 @@ runtime is properly initialized.
|
||||
|
||||
### Build
|
||||
|
||||
First build ghostty.dll, then compile the test:
|
||||
|
||||
```
|
||||
zig build -Dapp-runtime=none -Demit-exe=false
|
||||
zig cc test_dll_init.c -o test_dll_init.exe -target native-native-msvc
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
From this directory:
|
||||
|
||||
```
|
||||
copy ..\..\zig-out\lib\ghostty.dll . && test_dll_init.exe
|
||||
```
|
||||
@@ -24,5 +29,8 @@ Expected output (after the CRT fix):
|
||||
|
||||
```
|
||||
ghostty_info: <version string>
|
||||
ghostty_init: 0
|
||||
```
|
||||
|
||||
The ghostty_info call verifies the DLL loads and the CRT is initialized.
|
||||
Before the fix, loading the DLL would crash with "access violation writing
|
||||
0x0000000000000024".
|
||||
|
||||
@@ -2,16 +2,19 @@
|
||||
* Minimal reproducer for the libghostty DLL CRT initialization issue.
|
||||
*
|
||||
* Before the fix (DllMain calling __vcrt_initialize / __acrt_initialize),
|
||||
* ghostty_init crashed with "access violation writing 0x0000000000000024"
|
||||
* because Zig's _DllMainCRTStartup does not initialize the MSVC C runtime
|
||||
* for DLL targets.
|
||||
* loading ghostty.dll and calling any function that touches the C runtime
|
||||
* crashed with "access violation writing 0x0000000000000024" because Zig's
|
||||
* _DllMainCRTStartup does not initialize the MSVC C runtime for DLL targets.
|
||||
*
|
||||
* This test loads the DLL and calls ghostty_info, which exercises the CRT
|
||||
* (string handling, memory). If it returns a version string without
|
||||
* crashing, the CRT is properly initialized.
|
||||
*
|
||||
* Build: zig cc test_dll_init.c -o test_dll_init.exe -target native-native-msvc
|
||||
* Run: copy ..\..\zig-out\lib\ghostty.dll . && test_dll_init.exe
|
||||
*
|
||||
* Expected output (after fix):
|
||||
* ghostty_info: <version string>
|
||||
* ghostty_init: 0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
@@ -24,7 +27,6 @@ typedef struct {
|
||||
} ghostty_info_s;
|
||||
|
||||
typedef ghostty_info_s (*ghostty_info_fn)(void);
|
||||
typedef int (*ghostty_init_fn)(size_t, char **);
|
||||
|
||||
int main(void) {
|
||||
HMODULE dll = LoadLibraryA("ghostty.dll");
|
||||
@@ -34,18 +36,13 @@ int main(void) {
|
||||
}
|
||||
|
||||
ghostty_info_fn info_fn = (ghostty_info_fn)GetProcAddress(dll, "ghostty_info");
|
||||
if (info_fn) {
|
||||
ghostty_info_s info = info_fn();
|
||||
fprintf(stderr, "ghostty_info: %.*s\n", (int)info.version_len, info.version);
|
||||
if (!info_fn) {
|
||||
fprintf(stderr, "GetProcAddress(ghostty_info) failed: %lu\n", GetLastError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
ghostty_init_fn init_fn = (ghostty_init_fn)GetProcAddress(dll, "ghostty_init");
|
||||
if (init_fn) {
|
||||
char *argv[] = {"ghostty"};
|
||||
int result = init_fn(1, argv);
|
||||
fprintf(stderr, "ghostty_init: %d\n", result);
|
||||
if (result != 0) return 1;
|
||||
}
|
||||
ghostty_info_s info = info_fn();
|
||||
fprintf(stderr, "ghostty_info: %.*s\n", (int)info.version_len, info.version);
|
||||
|
||||
/* Skip FreeLibrary -- ghostty's global state cleanup and CRT
|
||||
* teardown ordering is not yet handled. The OS reclaims everything
|
||||
|
||||
Reference in New Issue
Block a user