Get GetStdHandle handle without a win API call

so when decompiling a basic zig hello world program, i discovered that it doesn’t use any external imports to get the stdout handle, it uses only numbers i believe, or some kind of PEB. im wondering how i can apply it to my code below and possibly making the write file call directly as nt, just trying to make a more minimal hello world than i already have

const std = @import("std");
const windows = std.os.windows;
const kernel32 = windows.kernel32;

const DWORD = windows.DWORD;
const HANDLE = windows.HANDLE;
const OVERLAPPED = windows.OVERLAPPED;

const message = "Hello World!\n";

extern "kernel32" fn GetStdHandle(
    nStdHandle: DWORD,
) HANDLE;

extern "kernel32" fn WriteFile(
    in_hFile: HANDLE,
    in_lpBuffer: [*]const u8,
    in_nNumberOfBytesToWrite: DWORD,
    out_lpNumberOfBytesWritten: ?*DWORD,
    in_out_lpOverlapped: ?*OVERLAPPED,
) void;

pub fn main() void {
    const stdout: HANDLE = GetStdHandle(windows.STD_OUTPUT_HANDLE);
    WriteFile(
        stdout,
        message,
        message.len,
        null,
        null,
    );
}

Why not look at what std.io.getStdOutHandle is doing?

Zig currently calls kernel32.WriteFile in std.os.windows.WriteFile (relevant issue), but we can check what ntdll functions are called in your program by running it with NtTrace:

NtWriteFile( FileHandle=0x2f4, Event=0, ApcRoutine=null, ApcContext=null, IoStatusBlock=0x2366fffa00 [0/0xd], Buffer=0xd0510, Length=0xd, ByteOffset=null, Key=null ) => 0

So in theory you should be able to replace your WriteFile with a NtWriteFile call using the handle gotten from the PEB and then your program will only depend on ntdll).


Side note unrelated to NtWriteFile: console output on Windows (specifically, things that use NtDeviceIoControlFile internally like SetConsoleOutputCP, WriteConsoleW, etc) is one place that depending on kernel32 might be necessary if you want the program to be able to be run via Wine, see the comments of std.start: initialize Windows console output CP to UTF-8 on exe startup by mlugg · Pull Request #14411 · ziglang/zig · GitHub for details

what’s the extern for NtWriteFile? its not in the std

All the bindings in the standard library are hand-written based on the documentation (or things like the Wine/ReactOS implementation if no documentation is available).

code review? it’s not showing the whole codebase, might wanna click the link

usize is not the right type for ULONG (ULONG is u32); I’d just use windows.ULONG. I’d also use ?windows.PVOID and windows.PVOID for ApcContext and Buffer, respectively (windows.PVOID is *anyopaque).

EDIT: Actually, [*]const u8 for Buffer seems fine since it’s explicitly a buffer of bytes. I’d still use ?windows.PVOID for ApcContext, though.