v / vlib / builtin / builtin_windows.c.v
411 lines · 367 sloc · 10.41 KB · 81a5657604ec6da99c25e26546870c6888d6fdde
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4@[has_globals]
5module builtin
6
7#include <io.h>
8#include <fcntl.h>
9
10// Cast the V callback to the Windows SDK callback type. Clang 20 treats the
11// otherwise-compatible struct pointer mismatch as a hard error for v_win.c.
12#define v_set_unhandled_exception_filter(handler) SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)(handler))
13
14// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
15// See https://www.codeproject.com/KB/string/cppstringguide1.aspx
16pub type C.BOOL = int
17pub type C.LONG = int
18
19pub type C.HINSTANCE = voidptr
20
21pub type C.HICON = voidptr
22
23pub type C.HCURSOR = voidptr
24
25pub type C.HBRUSH = voidptr
26
27pub type C.HWND = voidptr
28
29pub type C.HGLOBAL = voidptr
30
31pub type C.HANDLE = voidptr
32
33pub type C.LRESULT = voidptr
34
35pub type C.CHAR = char
36
37pub type C.TCHAR = u16 // It is u8 if UNICODE is not defined, but for V programs it always is
38
39pub type C.WCHAR = u16
40
41pub type C.LPSTR = &char
42
43pub type C.LPWSTR = &C.WCHAR
44
45pub type C.LPTSTR = &C.TCHAR
46
47pub type C.LPCTSTR = &C.TCHAR
48
49fn C.WriteConsoleW(voidptr, &u16, u32, &u32, voidptr) bool
50fn C.WriteFile(voidptr, &u8, u32, &u32, voidptr) bool
51fn C.ExitProcess(u32)
52fn C.GetProcessHeap() voidptr
53fn C.HeapAlloc(voidptr, u32, usize) voidptr
54fn C.HeapFree(voidptr, u32, voidptr) bool
55
56fn C._setmode(int, int) int
57
58// set_stream_binary_mode disables CRT newline translation for redirected stdio streams.
59fn set_stream_binary_mode(stream &C.FILE) {
60 fd := C._fileno(stream)
61 if fd >= 0 {
62 C._setmode(fd, C._O_BINARY)
63 }
64}
65
66fn is_terminal(fd int) int {
67 mut mode := u32(0)
68 $if v2_native_windows_pe_minimal ? {
69 if fd != 0 && fd != 1 && fd != 2 {
70 return 0
71 }
72 handle_id := if fd == 0 {
73 std_input_handle
74 } else if fd == 2 {
75 std_error_handle
76 } else {
77 std_output_handle
78 }
79 handle := C.GetStdHandle(handle_id)
80 if isnil(handle) || handle == voidptr(-1) {
81 return 0
82 }
83 if !C.GetConsoleMode(handle, &mode) {
84 return 0
85 }
86 return int(mode)
87 } $else {
88 osfh := voidptr(C._get_osfhandle(fd))
89 C.GetConsoleMode(osfh, voidptr(&mode))
90 return int(mode)
91 }
92}
93
94// GetStdHandle takes DWORD values. Microsoft defines these as ((DWORD)-N),
95// so spell the rollover values explicitly for native backends.
96const std_input_handle = u32(0xfffffff6)
97const std_output_handle = u32(0xfffffff5)
98const std_error_handle = u32(0xfffffff4)
99const enable_processed_output = 1
100const enable_wrap_at_eol_output = 2
101const evable_virtual_terminal_processing = 4
102
103// Write UTF-8 directly as UTF-16 for console hosts instead of changing the console code page.
104@[manualfree]
105fn write_buf_to_console(fd int, buf &u8, buf_len int) bool {
106 if buf_len <= 0 || is_terminal(fd) <= 0 {
107 return false
108 }
109 mut console_handle := C.GetStdHandle(std_output_handle)
110 if fd == 2 {
111 console_handle = C.GetStdHandle(std_error_handle)
112 }
113 if isnil(console_handle) || console_handle == voidptr(-1) {
114 return false
115 }
116 unsafe {
117 wide_len := C.MultiByteToWideChar(cp_utf8, 0, &char(buf), buf_len, 0, 0)
118 if wide_len <= 0 {
119 return false
120 }
121 mut wide_buf := &u16(malloc_noscan((wide_len + 1) * int(sizeof(u16))))
122 if isnil(wide_buf) {
123 return false
124 }
125 defer {
126 free(wide_buf)
127 }
128 converted := C.MultiByteToWideChar(cp_utf8, 0, &char(buf), buf_len, wide_buf, wide_len)
129 if converted <= 0 {
130 return false
131 }
132 wide_buf[converted] = 0
133 mut remaining_chars := converted
134 mut wide_ptr := wide_buf
135 for remaining_chars > 0 {
136 mut chars_written := u32(0)
137 if !C.WriteConsoleW(console_handle, wide_ptr, u32(remaining_chars), &chars_written, nil)
138 || chars_written == 0 {
139 return false
140 }
141 wide_ptr += int(chars_written)
142 remaining_chars -= int(chars_written)
143 }
144 return true
145 }
146}
147
148@[manualfree]
149fn write_buf_to_console_kernel32(fd int, buf &u8, buf_len int) bool {
150 if buf_len <= 0 || (fd != 1 && fd != 2) {
151 return false
152 }
153 console_handle := C.GetStdHandle(if fd == 2 { std_error_handle } else { std_output_handle })
154 if isnil(console_handle) || console_handle == voidptr(-1) {
155 return false
156 }
157 mut mode := u32(0)
158 if !C.GetConsoleMode(console_handle, &mode) {
159 return false
160 }
161 unsafe {
162 wide_len := C.MultiByteToWideChar(cp_utf8, 0, &char(buf), buf_len, 0, 0)
163 if wide_len <= 0 {
164 return false
165 }
166 heap := C.GetProcessHeap()
167 if isnil(heap) {
168 return false
169 }
170 wide_size := usize((wide_len + 1) * int(sizeof(u16)))
171 mut wide_buf := &u16(C.HeapAlloc(heap, 0, wide_size))
172 if isnil(wide_buf) {
173 return false
174 }
175 defer {
176 C.HeapFree(heap, 0, wide_buf)
177 }
178 converted := C.MultiByteToWideChar(cp_utf8, 0, &char(buf), buf_len, wide_buf, wide_len)
179 if converted <= 0 {
180 return false
181 }
182 wide_buf[converted] = 0
183 mut remaining_chars := converted
184 mut wide_ptr := wide_buf
185 for remaining_chars > 0 {
186 mut chars_written := u32(0)
187 if !C.WriteConsoleW(console_handle, wide_ptr, u32(remaining_chars), &chars_written, nil)
188 || chars_written == 0 {
189 return false
190 }
191 wide_ptr += int(chars_written)
192 remaining_chars -= int(chars_written)
193 }
194 return true
195 }
196}
197
198fn write_buf_to_fd_kernel32(fd int, buf &u8, buf_len int) bool {
199 return write_buf_to_fd_kernel32_status(fd, buf, buf_len) == 0
200}
201
202fn write_buf_to_fd_kernel32_status(fd int, buf &u8, buf_len int) int {
203 if buf_len <= 0 {
204 return 0
205 }
206 if fd != 1 && fd != 2 {
207 return 1
208 }
209 if write_buf_to_console_kernel32(fd, buf, buf_len) {
210 return 0
211 }
212 handle_id := if fd == 2 { std_error_handle } else { std_output_handle }
213 handle := C.GetStdHandle(handle_id)
214 if isnil(handle) || handle == voidptr(-1) {
215 return 2
216 }
217 mut ptr := unsafe { buf }
218 mut remaining_bytes := buf_len
219 unsafe {
220 for remaining_bytes > 0 {
221 chunk := if remaining_bytes > int(0x7fffffff) {
222 int(0x7fffffff)
223 } else {
224 remaining_bytes
225 }
226 mut written := u32(0)
227 if !C.WriteFile(handle, ptr, u32(chunk), &written, nil) {
228 return 3
229 }
230 if written == 0 {
231 return 4
232 }
233 ptr += int(written)
234 remaining_bytes -= int(written)
235 }
236 }
237 return 0
238}
239
240@[manualfree]
241fn write_buf_to_fd_windows_non_minimal(fd int, buf &u8, buf_len int) {
242 if buf_len <= 0 {
243 return
244 }
245 mut ptr := unsafe { buf }
246 mut remaining_bytes := isize(buf_len)
247 if write_buf_to_console(fd, ptr, int(remaining_bytes)) {
248 return
249 }
250 mut stream := voidptr(C.stdout)
251 if fd == 2 {
252 stream = voidptr(C.stderr)
253 }
254 mut x := isize(0)
255 unsafe {
256 for remaining_bytes > 0 {
257 x = isize(C.fwrite(ptr, 1, remaining_bytes, stream))
258 if x <= 0 {
259 // GUI programs on Windows may not have a writable stdout/stderr stream.
260 break
261 }
262 ptr += x
263 remaining_bytes -= x
264 }
265 }
266}
267
268fn write_buf_to_fd_kernel32_or_exit(fd int, buf &u8, buf_len int) {
269 write_status := write_buf_to_fd_kernel32_status(fd, buf, buf_len)
270 if write_status != 0 {
271 C.ExitProcess(u32(220 + write_status))
272 }
273}
274
275@[markused]
276fn builtin_init() {
277 $if v2_native_windows_pe_minimal ? {
278 return
279 } $else {
280 $if gcboehm ? {
281 $if !gc_warn_on_stderr ? {
282 gc_set_warn_proc(internal_gc_warn_proc_none)
283 }
284 }
285 set_stream_binary_mode(C.stdout)
286 set_stream_binary_mode(C.stderr)
287 if is_terminal(1) > 0 {
288 C.SetConsoleMode(C.GetStdHandle(std_output_handle),
289 enable_processed_output | enable_wrap_at_eol_output | evable_virtual_terminal_processing)
290 C.SetConsoleMode(C.GetStdHandle(std_error_handle),
291 enable_processed_output | enable_wrap_at_eol_output | evable_virtual_terminal_processing)
292 unsafe {
293 set_stream_unbuffered(C.stdout)
294 set_stream_unbuffered(C.stderr)
295 }
296 }
297 $if !no_backtrace ? {
298 add_unhandled_exception_handler()
299 }
300 // On windows, the default buffering is block based (~4096bytes), which interferes badly with non cmd shells
301 // It is much better to have it off by default instead.
302 unbuffer_stdout()
303 }
304}
305
306// TODO: copypaste from os
307// we want to be able to use this here without having to `import os`
308struct ExceptionRecord {
309pub:
310 // status_ constants
311 code u32
312 flags u32
313 record &ExceptionRecord = unsafe { nil }
314 address voidptr
315 param_count u32
316 // params []voidptr
317}
318
319struct ContextRecord {
320 // TODO
321}
322
323struct ExceptionPointers {
324pub:
325 exception_record &ExceptionRecord = unsafe { nil }
326 context_record &ContextRecord = unsafe { nil }
327}
328
329@[callconv: stdcall]
330type TopLevelExceptionFilter = fn (&ExceptionPointers) C.LONG
331
332fn C.SetUnhandledExceptionFilter(TopLevelExceptionFilter) voidptr
333fn C.v_set_unhandled_exception_filter(TopLevelExceptionFilter) voidptr
334
335@[callconv: stdcall]
336fn unhandled_exception_handler(e &ExceptionPointers) C.LONG {
337 match e.exception_record.code {
338 // These are 'used' by the backtrace printer
339 // so we dont want to catch them...
340 0x4001000A, 0x40010006, 0x406D1388, 0xE06D7363 {
341 return 0
342 }
343 else {
344 eprintln('Unhandled Exception 0x' + ptr_str(e.exception_record.code) + ' at ' +
345 ptr_str(e.exception_record.address))
346 flush_stdout()
347 flush_stderr()
348 print_backtrace_skipping_top_frames(5)
349 flush_stdout()
350 flush_stderr()
351 }
352 }
353
354 return 0
355}
356
357fn add_unhandled_exception_handler() {
358 // A vectored handler also sees first-chance exceptions that Windows APIs may
359 // handle internally, which can lead to false-positive "Unhandled Exception"
360 // reports. Register a top-level filter instead.
361 C.v_set_unhandled_exception_filter(unhandled_exception_handler)
362}
363
364fn C.IsDebuggerPresent() bool
365
366fn C.__debugbreak()
367
368fn break_if_debugger_attached() {
369 $if tinyc {
370 unsafe {
371 mut ptr := &voidptr(0)
372 *ptr = nil
373 _ = ptr
374 }
375 } $else {
376 if C.IsDebuggerPresent() {
377 C.__debugbreak()
378 }
379 }
380}
381
382const format_message_allocate_buffer = 0x00000100
383const format_message_argument_array = 0x00002000
384const format_message_from_hmodule = 0x00000800
385const format_message_from_string = 0x00000400
386const format_message_from_system = 0x00001000
387const format_message_ignore_inserts = 0x00000200
388
389// return an error message generated from WinAPI's `LastError`
390pub fn winapi_lasterr_str() string {
391 err_msg_id := C.GetLastError()
392 if err_msg_id == 8 {
393 // handle this case special since `FormatMessageW()` might not work anymore
394 return 'insufficient memory'
395 }
396 mut msgbuf := &u16(unsafe { nil })
397 res := C.FormatMessageW(format_message_allocate_buffer | format_message_from_system | format_message_ignore_inserts,
398 0, err_msg_id, 0, voidptr(&msgbuf), 0, 0)
399 err_msg := if res == 0 {
400 'Win-API error ${err_msg_id}'
401 } else {
402 unsafe { string_from_wide(msgbuf) }
403 }
404 return err_msg
405}
406
407// panic with an error message generated from WinAPI's `LastError`
408@[noreturn]
409pub fn panic_lasterr(base string) {
410 panic(base + winapi_lasterr_str())
411}
412