v2 / vlib / builtin / builtin_windows.c.v
251 lines · 212 sloc · 6.54 KB · b4339f8d50e1ed03c54114c6af20cff8d153a108
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, voidptr, voidptr) bool
50
51fn C._setmode(int, int) int
52
53// set_stream_binary_mode disables CRT newline translation for redirected stdio streams.
54fn set_stream_binary_mode(stream &C.FILE) {
55 fd := C._fileno(stream)
56 if fd >= 0 {
57 C._setmode(fd, C._O_BINARY)
58 }
59}
60
61fn is_terminal(fd int) int {
62 mut mode := u32(0)
63 osfh := voidptr(C._get_osfhandle(fd))
64 C.GetConsoleMode(osfh, voidptr(&mode))
65 return int(mode)
66}
67
68const std_output_handle = -11
69const std_error_handle = -12
70const enable_processed_output = 1
71const enable_wrap_at_eol_output = 2
72const evable_virtual_terminal_processing = 4
73
74// Write UTF-8 directly as UTF-16 for console hosts instead of changing the console code page.
75@[manualfree]
76fn write_buf_to_console(fd int, buf &u8, buf_len int) bool {
77 if buf_len <= 0 || is_terminal(fd) <= 0 {
78 return false
79 }
80 mut console_handle := C.GetStdHandle(std_output_handle)
81 if fd == 2 {
82 console_handle = C.GetStdHandle(std_error_handle)
83 }
84 if isnil(console_handle) {
85 return false
86 }
87 unsafe {
88 wide_len := C.MultiByteToWideChar(cp_utf8, 0, &char(buf), buf_len, 0, 0)
89 if wide_len <= 0 {
90 return false
91 }
92 mut wide_buf := &u16(malloc_noscan((wide_len + 1) * int(sizeof(u16))))
93 if isnil(wide_buf) {
94 return false
95 }
96 defer {
97 free(wide_buf)
98 }
99 converted := C.MultiByteToWideChar(cp_utf8, 0, &char(buf), buf_len, wide_buf, wide_len)
100 if converted <= 0 {
101 return false
102 }
103 wide_buf[converted] = 0
104 mut remaining_chars := converted
105 mut wide_ptr := wide_buf
106 for remaining_chars > 0 {
107 mut chars_written := u32(0)
108 if !C.WriteConsoleW(console_handle, wide_ptr, u32(remaining_chars), voidptr(&chars_written), nil)
109 || chars_written == 0 {
110 return false
111 }
112 wide_ptr += int(chars_written)
113 remaining_chars -= int(chars_written)
114 }
115 return true
116 }
117}
118
119@[markused]
120fn builtin_init() {
121 $if gcboehm ? {
122 $if !gc_warn_on_stderr ? {
123 gc_set_warn_proc(internal_gc_warn_proc_none)
124 }
125 }
126 set_stream_binary_mode(C.stdout)
127 set_stream_binary_mode(C.stderr)
128 if is_terminal(1) > 0 {
129 C.SetConsoleMode(C.GetStdHandle(std_output_handle),
130 enable_processed_output | enable_wrap_at_eol_output | evable_virtual_terminal_processing)
131 C.SetConsoleMode(C.GetStdHandle(std_error_handle),
132 enable_processed_output | enable_wrap_at_eol_output | evable_virtual_terminal_processing)
133 unsafe {
134 set_stream_unbuffered(C.stdout)
135 set_stream_unbuffered(C.stderr)
136 }
137 }
138 $if !no_backtrace ? {
139 add_unhandled_exception_handler()
140 }
141 // On windows, the default buffering is block based (~4096bytes), which interferes badly with non cmd shells
142 // It is much better to have it off by default instead.
143 unbuffer_stdout()
144}
145
146// TODO: copypaste from os
147// we want to be able to use this here without having to `import os`
148struct ExceptionRecord {
149pub:
150 // status_ constants
151 code u32
152 flags u32
153 record &ExceptionRecord = unsafe { nil }
154 address voidptr
155 param_count u32
156 // params []voidptr
157}
158
159struct ContextRecord {
160 // TODO
161}
162
163struct ExceptionPointers {
164pub:
165 exception_record &ExceptionRecord = unsafe { nil }
166 context_record &ContextRecord = unsafe { nil }
167}
168
169@[callconv: stdcall]
170type TopLevelExceptionFilter = fn (&ExceptionPointers) C.LONG
171
172fn C.SetUnhandledExceptionFilter(TopLevelExceptionFilter) voidptr
173fn C.v_set_unhandled_exception_filter(TopLevelExceptionFilter) voidptr
174
175@[callconv: stdcall]
176fn unhandled_exception_handler(e &ExceptionPointers) C.LONG {
177 match e.exception_record.code {
178 // These are 'used' by the backtrace printer
179 // so we dont want to catch them...
180 0x4001000A, 0x40010006, 0x406D1388, 0xE06D7363 {
181 return 0
182 }
183 else {
184 eprintln('Unhandled Exception 0x' + ptr_str(e.exception_record.code) + ' at ' +
185 ptr_str(e.exception_record.address))
186 flush_stdout()
187 flush_stderr()
188 print_backtrace_skipping_top_frames(5)
189 flush_stdout()
190 flush_stderr()
191 }
192 }
193
194 return 0
195}
196
197fn add_unhandled_exception_handler() {
198 // A vectored handler also sees first-chance exceptions that Windows APIs may
199 // handle internally, which can lead to false-positive "Unhandled Exception"
200 // reports. Register a top-level filter instead.
201 C.v_set_unhandled_exception_filter(unhandled_exception_handler)
202}
203
204fn C.IsDebuggerPresent() bool
205
206fn C.__debugbreak()
207
208fn break_if_debugger_attached() {
209 $if tinyc {
210 unsafe {
211 mut ptr := &voidptr(0)
212 *ptr = nil
213 _ = ptr
214 }
215 } $else {
216 if C.IsDebuggerPresent() {
217 C.__debugbreak()
218 }
219 }
220}
221
222const format_message_allocate_buffer = 0x00000100
223const format_message_argument_array = 0x00002000
224const format_message_from_hmodule = 0x00000800
225const format_message_from_string = 0x00000400
226const format_message_from_system = 0x00001000
227const format_message_ignore_inserts = 0x00000200
228
229// return an error message generated from WinAPI's `LastError`
230pub fn winapi_lasterr_str() string {
231 err_msg_id := C.GetLastError()
232 if err_msg_id == 8 {
233 // handle this case special since `FormatMessageW()` might not work anymore
234 return 'insufficient memory'
235 }
236 mut msgbuf := &u16(unsafe { nil })
237 res := C.FormatMessageW(format_message_allocate_buffer | format_message_from_system | format_message_ignore_inserts,
238 0, err_msg_id, 0, voidptr(&msgbuf), 0, 0)
239 err_msg := if res == 0 {
240 'Win-API error ${err_msg_id}'
241 } else {
242 unsafe { string_from_wide(msgbuf) }
243 }
244 return err_msg
245}
246
247// panic with an error message generated from WinAPI's `LastError`
248@[noreturn]
249pub fn panic_lasterr(base string) {
250 panic(base + winapi_lasterr_str())
251}
252