Commit 92f7474359

Andrew Kelley <superjoe30@gmail.com>
2018-09-03 05:25:04
switch most windows calls to use W versions instead of A
See #534
1 parent d596808
std/os/windows/index.zig
@@ -6,8 +6,6 @@ pub use @import("kernel32.zig");
 pub use @import("ntdll.zig");
 pub use @import("ole32.zig");
 pub use @import("shell32.zig");
-pub use @import("shlwapi.zig");
-pub use @import("user32.zig");
 
 test "import" {
     _ = @import("util.zig");
@@ -174,11 +172,11 @@ pub const PROCESS_INFORMATION = extern struct {
     dwThreadId: DWORD,
 };
 
-pub const STARTUPINFOA = extern struct {
+pub const STARTUPINFOW = extern struct {
     cb: DWORD,
-    lpReserved: ?LPSTR,
-    lpDesktop: ?LPSTR,
-    lpTitle: ?LPSTR,
+    lpReserved: ?LPWSTR,
+    lpDesktop: ?LPWSTR,
+    lpTitle: ?LPWSTR,
     dwX: DWORD,
     dwY: DWORD,
     dwXSize: DWORD,
@@ -238,7 +236,7 @@ pub const HEAP_NO_SERIALIZE = 0x00000001;
 pub const PTHREAD_START_ROUTINE = extern fn (LPVOID) DWORD;
 pub const LPTHREAD_START_ROUTINE = PTHREAD_START_ROUTINE;
 
-pub const WIN32_FIND_DATAA = extern struct {
+pub const WIN32_FIND_DATAW = extern struct {
     dwFileAttributes: DWORD,
     ftCreationTime: FILETIME,
     ftLastAccessTime: FILETIME,
@@ -247,8 +245,8 @@ pub const WIN32_FIND_DATAA = extern struct {
     nFileSizeLow: DWORD,
     dwReserved0: DWORD,
     dwReserved1: DWORD,
-    cFileName: [260]CHAR,
-    cAlternateFileName: [14]CHAR,
+    cFileName: [260]u16,
+    cAlternateFileName: [14]u16,
 };
 
 pub const FILETIME = extern struct {
@@ -377,3 +375,5 @@ pub const COORD = extern struct {
     X: SHORT,
     Y: SHORT,
 };
+
+pub const CREATE_UNICODE_ENVIRONMENT = 1024;
std/os/windows/kernel32.zig
@@ -4,19 +4,8 @@ pub extern "kernel32" stdcallcc fn CancelIoEx(hFile: HANDLE, lpOverlapped: LPOVE
 
 pub extern "kernel32" stdcallcc fn CloseHandle(hObject: HANDLE) BOOL;
 
-pub extern "kernel32" stdcallcc fn CreateDirectoryA(lpPathName: [*]const u8, lpSecurityAttributes: ?*SECURITY_ATTRIBUTES) BOOL;
 pub extern "kernel32" stdcallcc fn CreateDirectoryW(lpPathName: [*]const u16, lpSecurityAttributes: ?*SECURITY_ATTRIBUTES) BOOL;
 
-pub extern "kernel32" stdcallcc fn CreateFileA(
-    lpFileName: [*]const u8, // TODO null terminated pointer type
-    dwDesiredAccess: DWORD,
-    dwShareMode: DWORD,
-    lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES,
-    dwCreationDisposition: DWORD,
-    dwFlagsAndAttributes: DWORD,
-    hTemplateFile: ?HANDLE,
-) HANDLE;
-
 pub extern "kernel32" stdcallcc fn CreateFileW(
     lpFileName: [*]const u16, // TODO null terminated pointer type
     dwDesiredAccess: DWORD,
@@ -34,37 +23,32 @@ pub extern "kernel32" stdcallcc fn CreatePipe(
     nSize: DWORD,
 ) BOOL;
 
-pub extern "kernel32" stdcallcc fn CreateProcessA(
-    lpApplicationName: ?LPCSTR,
-    lpCommandLine: LPSTR,
+pub extern "kernel32" stdcallcc fn CreateProcessW(
+    lpApplicationName: ?LPWSTR,
+    lpCommandLine: LPWSTR,
     lpProcessAttributes: ?*SECURITY_ATTRIBUTES,
     lpThreadAttributes: ?*SECURITY_ATTRIBUTES,
     bInheritHandles: BOOL,
     dwCreationFlags: DWORD,
     lpEnvironment: ?*c_void,
-    lpCurrentDirectory: ?LPCSTR,
-    lpStartupInfo: *STARTUPINFOA,
+    lpCurrentDirectory: ?LPWSTR,
+    lpStartupInfo: *STARTUPINFOW,
     lpProcessInformation: *PROCESS_INFORMATION,
 ) BOOL;
 
-pub extern "kernel32" stdcallcc fn CreateSymbolicLinkA(
-    lpSymlinkFileName: LPCSTR,
-    lpTargetFileName: LPCSTR,
-    dwFlags: DWORD,
-) BOOLEAN;
+pub extern "kernel32" stdcallcc fn CreateSymbolicLinkW(lpSymlinkFileName: [*]const u16, lpTargetFileName: [*]const u16, dwFlags: DWORD) BOOLEAN;
 
 pub extern "kernel32" stdcallcc fn CreateIoCompletionPort(FileHandle: HANDLE, ExistingCompletionPort: ?HANDLE, CompletionKey: ULONG_PTR, NumberOfConcurrentThreads: DWORD) ?HANDLE;
 
 pub extern "kernel32" stdcallcc fn CreateThread(lpThreadAttributes: ?LPSECURITY_ATTRIBUTES, dwStackSize: SIZE_T, lpStartAddress: LPTHREAD_START_ROUTINE, lpParameter: ?LPVOID, dwCreationFlags: DWORD, lpThreadId: ?LPDWORD) ?HANDLE;
 
-pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: [*]const u8) BOOL;
 pub extern "kernel32" stdcallcc fn DeleteFileW(lpFileName: [*]const u16) BOOL;
 
 pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn;
 
-pub extern "kernel32" stdcallcc fn FindFirstFileA(lpFileName: LPCSTR, lpFindFileData: *WIN32_FIND_DATAA) HANDLE;
+pub extern "kernel32" stdcallcc fn FindFirstFileW(lpFileName: [*]const u16, lpFindFileData: *WIN32_FIND_DATAW) HANDLE;
 pub extern "kernel32" stdcallcc fn FindClose(hFindFile: HANDLE) BOOL;
-pub extern "kernel32" stdcallcc fn FindNextFileA(hFindFile: HANDLE, lpFindFileData: *WIN32_FIND_DATAA) BOOL;
+pub extern "kernel32" stdcallcc fn FindNextFileW(hFindFile: HANDLE, lpFindFileData: *WIN32_FIND_DATAW) BOOL;
 
 pub extern "kernel32" stdcallcc fn FreeEnvironmentStringsA(penv: [*]u8) BOOL;
 
@@ -74,7 +58,6 @@ pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out
 
 pub extern "kernel32" stdcallcc fn GetConsoleScreenBufferInfo(hConsoleOutput: HANDLE, lpConsoleScreenBufferInfo: *CONSOLE_SCREEN_BUFFER_INFO) BOOL;
 
-pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: DWORD, lpBuffer: ?[*]CHAR) DWORD;
 pub extern "kernel32" stdcallcc fn GetCurrentDirectoryW(nBufferLength: DWORD, lpBuffer: ?[*]WCHAR) DWORD;
 
 pub extern "kernel32" stdcallcc fn GetCurrentThread() HANDLE;
@@ -88,10 +71,8 @@ pub extern "kernel32" stdcallcc fn GetExitCodeProcess(hProcess: HANDLE, lpExitCo
 
 pub extern "kernel32" stdcallcc fn GetFileSizeEx(hFile: HANDLE, lpFileSize: *LARGE_INTEGER) BOOL;
 
-pub extern "kernel32" stdcallcc fn GetFileAttributesA(lpFileName: [*]const CHAR) DWORD;
 pub extern "kernel32" stdcallcc fn GetFileAttributesW(lpFileName: [*]const WCHAR) DWORD;
 
-pub extern "kernel32" stdcallcc fn GetModuleFileNameA(hModule: ?HMODULE, lpFilename: [*]u8, nSize: DWORD) DWORD;
 pub extern "kernel32" stdcallcc fn GetModuleFileNameW(hModule: ?HMODULE, lpFilename: [*]u16, nSize: DWORD) DWORD;
 
 pub extern "kernel32" stdcallcc fn GetModuleHandleW(lpModuleName: ?[*]const WCHAR) HMODULE;
@@ -105,13 +86,6 @@ pub extern "kernel32" stdcallcc fn GetFileInformationByHandleEx(
     in_dwBufferSize: DWORD,
 ) BOOL;
 
-pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA(
-    hFile: HANDLE,
-    lpszFilePath: LPSTR,
-    cchFilePath: DWORD,
-    dwFlags: DWORD,
-) DWORD;
-
 pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleW(
     hFile: HANDLE,
     lpszFilePath: [*]u16,
@@ -142,12 +116,6 @@ pub extern "kernel32" stdcallcc fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem
 
 pub extern "kernel32" stdcallcc fn HeapValidate(hHeap: HANDLE, dwFlags: DWORD, lpMem: ?*const c_void) BOOL;
 
-pub extern "kernel32" stdcallcc fn MoveFileExA(
-    lpExistingFileName: [*]const u8,
-    lpNewFileName: [*]const u8,
-    dwFlags: DWORD,
-) BOOL;
-
 pub extern "kernel32" stdcallcc fn MoveFileExW(
     lpExistingFileName: [*]const u16,
     lpNewFileName: [*]const u16,
@@ -179,7 +147,7 @@ pub extern "kernel32" stdcallcc fn ReadFile(
     in_out_lpOverlapped: ?*OVERLAPPED,
 ) BOOL;
 
-pub extern "kernel32" stdcallcc fn RemoveDirectoryA(lpPathName: LPCSTR) BOOL;
+pub extern "kernel32" stdcallcc fn RemoveDirectoryW(lpPathName: [*]const u16) BOOL;
 
 pub extern "kernel32" stdcallcc fn SetConsoleTextAttribute(hConsoleOutput: HANDLE, wAttributes: WORD) BOOL;
 
@@ -208,8 +176,7 @@ pub extern "kernel32" stdcallcc fn WriteFile(
 
 pub extern "kernel32" stdcallcc fn WriteFileEx(hFile: HANDLE, lpBuffer: [*]const u8, nNumberOfBytesToWrite: DWORD, lpOverlapped: LPOVERLAPPED, lpCompletionRoutine: LPOVERLAPPED_COMPLETION_ROUTINE) BOOL;
 
-//TODO: call unicode versions instead of relying on ANSI code page
-pub extern "kernel32" stdcallcc fn LoadLibraryA(lpLibFileName: LPCSTR) ?HMODULE;
+pub extern "kernel32" stdcallcc fn LoadLibraryW(lpLibFileName: [*]const u16) ?HMODULE;
 
 pub extern "kernel32" stdcallcc fn FreeLibrary(hModule: HMODULE) BOOL;
 
std/os/windows/shlwapi.zig
@@ -1,4 +0,0 @@
-use @import("index.zig");
-
-pub extern "shlwapi" stdcallcc fn PathFileExistsA(pszPath: ?LPCTSTR) BOOL;
-
std/os/windows/user32.zig
@@ -1,4 +0,0 @@
-use @import("index.zig");
-
-pub extern "user32" stdcallcc fn MessageBoxA(hWnd: ?HANDLE, lpText: ?LPCTSTR, lpCaption: ?LPCTSTR, uType: UINT) c_int;
-
std/os/windows/util.zig
@@ -1,6 +1,7 @@
 const std = @import("../../index.zig");
 const builtin = @import("builtin");
 const os = std.os;
+const unicode = std.unicode;
 const windows = std.os.windows;
 const assert = std.debug.assert;
 const mem = std.mem;
@@ -156,41 +157,51 @@ pub fn windowsOpen(
 }
 
 /// Caller must free result.
-pub fn createWindowsEnvBlock(allocator: *mem.Allocator, env_map: *const BufMap) ![]u8 {
+pub fn createWindowsEnvBlock(allocator: *mem.Allocator, env_map: *const BufMap) ![]u16 {
     // count bytes needed
-    const bytes_needed = x: {
-        var bytes_needed: usize = 1; // 1 for the final null byte
+    const max_chars_needed = x: {
+        var max_chars_needed: usize = 1; // 1 for the final null byte
         var it = env_map.iterator();
         while (it.next()) |pair| {
             // +1 for '='
             // +1 for null byte
-            bytes_needed += pair.key.len + pair.value.len + 2;
+            max_chars_needed += pair.key.len + pair.value.len + 2;
         }
-        break :x bytes_needed;
+        break :x max_chars_needed;
     };
-    const result = try allocator.alloc(u8, bytes_needed);
+    const result = try allocator.alloc(u16, max_chars_needed);
     errdefer allocator.free(result);
 
     var it = env_map.iterator();
     var i: usize = 0;
     while (it.next()) |pair| {
-        mem.copy(u8, result[i..], pair.key);
-        i += pair.key.len;
+        i += try unicode.utf8ToUtf16Le(result[i..], pair.key);
         result[i] = '=';
         i += 1;
-        mem.copy(u8, result[i..], pair.value);
-        i += pair.value.len;
+        i += try unicode.utf8ToUtf16Le(result[i..], pair.value);
         result[i] = 0;
         i += 1;
     }
     result[i] = 0;
-    return result;
+    i += 1;
+    return allocator.shrink(u16, result, i);
 }
 
-pub fn windowsLoadDll(allocator: *mem.Allocator, dll_path: []const u8) !windows.HMODULE {
-    const padded_buff = try cstr.addNullByte(allocator, dll_path);
-    defer allocator.free(padded_buff);
-    return windows.LoadLibraryA(padded_buff.ptr) orelse error.DllNotFound;
+pub fn windowsLoadDllW(dll_path_w: [*]const u16) !windows.HMODULE {
+    return windows.LoadLibraryW(dll_path_w) orelse {
+        const err = windows.GetLastError();
+        switch (err) {
+            windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
+            windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
+            windows.ERROR.MOD_NOT_FOUND => return error.FileNotFound,
+            else => return os.unexpectedErrorWindows(err),
+        }
+    };
+}
+
+pub fn windowsLoadDll(dll_path: []const u8) !windows.HMODULE {
+    const dll_path_w = try sliceToPrefixedFileW(dll_path);
+    return windowsLoadDllW(&dll_path_w);
 }
 
 pub fn windowsUnloadDll(hModule: windows.HMODULE) void {
@@ -200,27 +211,19 @@ pub fn windowsUnloadDll(hModule: windows.HMODULE) void {
 test "InvalidDll" {
     if (builtin.os != builtin.Os.windows) return error.SkipZigTest;
 
-    const DllName = "asdf.dll";
-    const allocator = std.debug.global_allocator;
-    const handle = os.windowsLoadDll(allocator, DllName) catch |err| {
-        assert(err == error.DllNotFound);
+    const handle = os.windowsLoadDll("asdf.dll") catch |err| {
+        assert(err == error.FileNotFound);
         return;
     };
+    @panic("Expected error from function");
 }
 
 pub fn windowsFindFirstFile(
-    allocator: *mem.Allocator,
     dir_path: []const u8,
-    find_file_data: *windows.WIN32_FIND_DATAA,
+    find_file_data: *windows.WIN32_FIND_DATAW,
 ) !windows.HANDLE {
-    const wild_and_null = []u8{ '\\', '*', 0 };
-    const path_with_wild_and_null = try allocator.alloc(u8, dir_path.len + wild_and_null.len);
-    defer allocator.free(path_with_wild_and_null);
-
-    mem.copy(u8, path_with_wild_and_null, dir_path);
-    mem.copy(u8, path_with_wild_and_null[dir_path.len..], wild_and_null);
-
-    const handle = windows.FindFirstFileA(path_with_wild_and_null.ptr, find_file_data);
+    const dir_path_w = try sliceToPrefixedSuffixedFileW(dir_path, []u16{'\\', '*', 0});
+    const handle = windows.FindFirstFileW(&dir_path_w, find_file_data);
 
     if (handle == windows.INVALID_HANDLE_VALUE) {
         const err = windows.GetLastError();
@@ -235,8 +238,8 @@ pub fn windowsFindFirstFile(
 }
 
 /// Returns `true` if there was another file, `false` otherwise.
-pub fn windowsFindNextFile(handle: windows.HANDLE, find_file_data: *windows.WIN32_FIND_DATAA) !bool {
-    if (windows.FindNextFileA(handle, find_file_data) == 0) {
+pub fn windowsFindNextFile(handle: windows.HANDLE, find_file_data: *windows.WIN32_FIND_DATAW) !bool {
+    if (windows.FindNextFileW(handle, find_file_data) == 0) {
         const err = windows.GetLastError();
         return switch (err) {
             windows.ERROR.NO_MORE_FILES => false,
@@ -297,8 +300,12 @@ pub fn cStrToPrefixedFileW(s: [*]const u8) ![PATH_MAX_WIDE + 1]u16 {
 }
 
 pub fn sliceToPrefixedFileW(s: []const u8) ![PATH_MAX_WIDE + 1]u16 {
+    return sliceToPrefixedSuffixedFileW(s, []u16{0});
+}
+
+pub fn sliceToPrefixedSuffixedFileW(s: []const u8, comptime suffix: []const u16) ![PATH_MAX_WIDE + suffix.len]u16 {
     // TODO well defined copy elision
-    var result: [PATH_MAX_WIDE + 1]u16 = undefined;
+    var result: [PATH_MAX_WIDE + suffix.len]u16 = undefined;
 
     // > File I/O functions in the Windows API convert "/" to "\" as part of
     // > converting the name to an NT-style name, except when using the "\\?\"
@@ -306,11 +313,12 @@ pub fn sliceToPrefixedFileW(s: []const u8) ![PATH_MAX_WIDE + 1]u16 {
     // from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation
     // Because we want the larger maximum path length for absolute paths, we
     // disallow forward slashes in zig std lib file functions on Windows.
-    for (s) |byte|
+    for (s) |byte| {
         switch (byte) {
-        '/', '*', '?', '"', '<', '>', '|' => return error.BadPathName,
-        else => {},
-    };
+            '/', '*', '?', '"', '<', '>', '|' => return error.BadPathName,
+            else => {},
+        }
+    }
     const start_index = if (mem.startsWith(u8, s, "\\\\") or !os.path.isAbsolute(s)) 0 else blk: {
         const prefix = []u16{ '\\', '\\', '?', '\\' };
         mem.copy(u16, result[0..], prefix);
@@ -318,7 +326,7 @@ pub fn sliceToPrefixedFileW(s: []const u8) ![PATH_MAX_WIDE + 1]u16 {
     };
     const end_index = start_index + try std.unicode.utf8ToUtf16Le(result[start_index..], s);
     assert(end_index <= result.len);
-    if (end_index == result.len) return error.NameTooLong;
-    result[end_index] = 0;
+    if (end_index + suffix.len > result.len) return error.NameTooLong;
+    mem.copy(u16, result[end_index..], suffix);
     return result;
 }
std/os/child_process.zig
@@ -1,5 +1,6 @@
 const std = @import("../index.zig");
 const cstr = std.cstr;
+const unicode = std.unicode;
 const io = std.io;
 const os = std.os;
 const posix = os.posix;
@@ -12,6 +13,7 @@ const Buffer = std.Buffer;
 const builtin = @import("builtin");
 const Os = builtin.Os;
 const LinkedList = std.LinkedList;
+const windows_util = @import("windows/util.zig");
 
 const is_windows = builtin.os == Os.windows;
 
@@ -520,8 +522,8 @@ pub const ChildProcess = struct {
         const cmd_line = try windowsCreateCommandLine(self.allocator, self.argv);
         defer self.allocator.free(cmd_line);
 
-        var siStartInfo = windows.STARTUPINFOA{
-            .cb = @sizeOf(windows.STARTUPINFOA),
+        var siStartInfo = windows.STARTUPINFOW{
+            .cb = @sizeOf(windows.STARTUPINFOW),
             .hStdError = g_hChildStd_ERR_Wr,
             .hStdOutput = g_hChildStd_OUT_Wr,
             .hStdInput = g_hChildStd_IN_Rd,
@@ -545,7 +547,9 @@ pub const ChildProcess = struct {
 
         const cwd_slice = if (self.cwd) |cwd| try cstr.addNullByte(self.allocator, cwd) else null;
         defer if (cwd_slice) |cwd| self.allocator.free(cwd);
-        const cwd_ptr = if (cwd_slice) |cwd| cwd.ptr else null;
+        const cwd_w = if (cwd_slice) |cwd| try unicode.utf8ToUtf16LeWithNull(self.allocator, cwd) else null;
+        defer if (cwd_w) |cwd| self.allocator.free(cwd);
+        const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
 
         const maybe_envp_buf = if (self.env_map) |env_map| try os.createWindowsEnvBlock(self.allocator, env_map) else null;
         defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf);
@@ -564,7 +568,13 @@ pub const ChildProcess = struct {
         };
         defer self.allocator.free(app_name);
 
-        windowsCreateProcess(app_name.ptr, cmd_line.ptr, envp_ptr, cwd_ptr, &siStartInfo, &piProcInfo) catch |no_path_err| {
+        const app_name_w = try unicode.utf8ToUtf16LeWithNull(self.allocator, app_name);
+        defer self.allocator.free(app_name_w);
+
+        const cmd_line_w = try unicode.utf8ToUtf16LeWithNull(self.allocator, cmd_line);
+        defer self.allocator.free(cmd_line_w);
+
+        windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo) catch |no_path_err| {
             if (no_path_err != error.FileNotFound) return no_path_err;
 
             const PATH = try os.getEnvVarOwned(self.allocator, "PATH");
@@ -575,7 +585,10 @@ pub const ChildProcess = struct {
                 const joined_path = try os.path.join(self.allocator, search_path, app_name);
                 defer self.allocator.free(joined_path);
 
-                if (windowsCreateProcess(joined_path.ptr, cmd_line.ptr, envp_ptr, cwd_ptr, &siStartInfo, &piProcInfo)) |_| {
+                const joined_path_w = try unicode.utf8ToUtf16LeWithNull(self.allocator, app_name);
+                defer self.allocator.free(joined_path_w);
+
+                if (windowsCreateProcess(joined_path_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo)) |_| {
                     break;
                 } else |err| if (err == error.FileNotFound) {
                     continue;
@@ -626,15 +639,36 @@ pub const ChildProcess = struct {
     }
 };
 
-fn windowsCreateProcess(app_name: [*]u8, cmd_line: [*]u8, envp_ptr: ?[*]u8, cwd_ptr: ?[*]u8, lpStartupInfo: *windows.STARTUPINFOA, lpProcessInformation: *windows.PROCESS_INFORMATION) !void {
-    if (windows.CreateProcessA(app_name, cmd_line, null, null, windows.TRUE, 0, @ptrCast(?*c_void, envp_ptr), cwd_ptr, lpStartupInfo, lpProcessInformation) == 0) {
+fn windowsCreateProcess(app_name: [*]u16, cmd_line: [*]u16, envp_ptr: ?[*]u16, cwd_ptr: ?[*]u16, lpStartupInfo: *windows.STARTUPINFOW, lpProcessInformation: *windows.PROCESS_INFORMATION) !void {
+    // TODO the docs for environment pointer say:
+    // > A pointer to the environment block for the new process. If this parameter
+    // > is NULL, the new process uses the environment of the calling process.
+    // > ...
+    // > An environment block can contain either Unicode or ANSI characters. If
+    // > the environment block pointed to by lpEnvironment contains Unicode
+    // > characters, be sure that dwCreationFlags includes CREATE_UNICODE_ENVIRONMENT.
+    // > If this parameter is NULL and the environment block of the parent process
+    // > contains Unicode characters, you must also ensure that dwCreationFlags
+    // > includes CREATE_UNICODE_ENVIRONMENT.
+    // This seems to imply that we have to somehow know whether our process parent passed
+    // CREATE_UNICODE_ENVIRONMENT if we want to pass NULL for the environment parameter.
+    // Since we do not know this information that would imply that we must not pass NULL
+    // for the parameter.
+    // However this would imply that programs compiled with -DUNICODE could not pass
+    // environment variables to programs that were not, which seems unlikely.
+    // More investigation is needed.
+    if (windows.CreateProcessW(
+        app_name, cmd_line, null, null, windows.TRUE, windows.CREATE_UNICODE_ENVIRONMENT,
+        @ptrCast(?*c_void, envp_ptr), cwd_ptr, lpStartupInfo, lpProcessInformation,
+    ) == 0) {
         const err = windows.GetLastError();
-        return switch (err) {
-            windows.ERROR.FILE_NOT_FOUND, windows.ERROR.PATH_NOT_FOUND => error.FileNotFound,
+        switch (err) {
+            windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
+            windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
             windows.ERROR.INVALID_PARAMETER => unreachable,
-            windows.ERROR.INVALID_NAME => error.InvalidName,
-            else => os.unexpectedErrorWindows(err),
-        };
+            windows.ERROR.INVALID_NAME => return error.InvalidName,
+            else => return os.unexpectedErrorWindows(err),
+        }
     }
 }
 
std/os/index.zig
@@ -819,37 +819,40 @@ test "os.getCwd" {
 
 pub const SymLinkError = PosixSymLinkError || WindowsSymLinkError;
 
-pub fn symLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) SymLinkError!void {
+/// TODO add a symLinkC variant
+pub fn symLink(existing_path: []const u8, new_path: []const u8) SymLinkError!void {
     if (is_windows) {
-        return symLinkWindows(allocator, existing_path, new_path);
+        return symLinkWindows(existing_path, new_path);
     } else {
-        return symLinkPosix(allocator, existing_path, new_path);
+        return symLinkPosix(existing_path, new_path);
     }
 }
 
 pub const WindowsSymLinkError = error{
-    OutOfMemory,
+    NameTooLong,
+    InvalidUtf8,
+    BadPathName,
 
     /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
-pub fn symLinkWindows(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) WindowsSymLinkError!void {
-    const existing_with_null = try cstr.addNullByte(allocator, existing_path);
-    defer allocator.free(existing_with_null);
-    const new_with_null = try cstr.addNullByte(allocator, new_path);
-    defer allocator.free(new_with_null);
-
-    if (windows.CreateSymbolicLinkA(existing_with_null.ptr, new_with_null.ptr, 0) == 0) {
+pub fn symLinkW(existing_path_w: [*]const u16, new_path_w: [*]const u16) WindowsSymLinkError!void {
+    if (windows.CreateSymbolicLinkW(existing_path_w, new_path_w, 0) == 0) {
         const err = windows.GetLastError();
-        return switch (err) {
-            else => unexpectedErrorWindows(err),
-        };
+        switch (err) {
+            else => return unexpectedErrorWindows(err),
+        }
     }
 }
 
+pub fn symLinkWindows(existing_path: []const u8, new_path: []const u8) WindowsSymLinkError!void {
+    const existing_path_w = try windows_util.sliceToPrefixedFileW(existing_path);
+    const new_path_w = try windows_util.sliceToPrefixedFileW(new_path);
+    return symLinkW(&existing_path_w, &new_path_w);
+}
+
 pub const PosixSymLinkError = error{
-    OutOfMemory,
     AccessDenied,
     DiskQuota,
     PathAlreadyExists,
@@ -866,43 +869,40 @@ pub const PosixSymLinkError = error{
     Unexpected,
 };
 
-pub fn symLinkPosix(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) PosixSymLinkError!void {
-    const full_buf = try allocator.alloc(u8, existing_path.len + new_path.len + 2);
-    defer allocator.free(full_buf);
-
-    const existing_buf = full_buf;
-    mem.copy(u8, existing_buf, existing_path);
-    existing_buf[existing_path.len] = 0;
-
-    const new_buf = full_buf[existing_path.len + 1 ..];
-    mem.copy(u8, new_buf, new_path);
-    new_buf[new_path.len] = 0;
-
-    const err = posix.getErrno(posix.symlink(existing_buf.ptr, new_buf.ptr));
-    if (err > 0) {
-        return switch (err) {
-            posix.EFAULT, posix.EINVAL => unreachable,
-            posix.EACCES, posix.EPERM => error.AccessDenied,
-            posix.EDQUOT => error.DiskQuota,
-            posix.EEXIST => error.PathAlreadyExists,
-            posix.EIO => error.FileSystem,
-            posix.ELOOP => error.SymLinkLoop,
-            posix.ENAMETOOLONG => error.NameTooLong,
-            posix.ENOENT => error.FileNotFound,
-            posix.ENOTDIR => error.NotDir,
-            posix.ENOMEM => error.SystemResources,
-            posix.ENOSPC => error.NoSpaceLeft,
-            posix.EROFS => error.ReadOnlyFileSystem,
-            else => unexpectedErrorPosix(err),
-        };
+pub fn symLinkPosixC(existing_path: [*]const u8, new_path: [*]const u8) PosixSymLinkError!void {
+    const err = posix.getErrno(posix.symlink(existing_path, new_path));
+    switch (err) {
+        0 => return,
+        posix.EFAULT => unreachable,
+        posix.EINVAL => unreachable,
+        posix.EACCES => return error.AccessDenied,
+        posix.EPERM => return error.AccessDenied,
+        posix.EDQUOT => return error.DiskQuota,
+        posix.EEXIST => return error.PathAlreadyExists,
+        posix.EIO => return error.FileSystem,
+        posix.ELOOP => return error.SymLinkLoop,
+        posix.ENAMETOOLONG => return error.NameTooLong,
+        posix.ENOENT => return error.FileNotFound,
+        posix.ENOTDIR => return error.NotDir,
+        posix.ENOMEM => return error.SystemResources,
+        posix.ENOSPC => return error.NoSpaceLeft,
+        posix.EROFS => return error.ReadOnlyFileSystem,
+        else => return unexpectedErrorPosix(err),
     }
 }
 
+pub fn symLinkPosix(existing_path: []const u8, new_path: []const u8) PosixSymLinkError!void {
+    const existing_path_c = try toPosixPath(existing_path);
+    const new_path_c = try toPosixPath(new_path);
+    return symLinkPosixC(&existing_path_c, &new_path_c);
+}
+
 // here we replace the standard +/ with -_ so that it can be used in a file name
 const b64_fs_encoder = base64.Base64Encoder.init("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", base64.standard_pad_char);
 
+/// TODO remove the allocator requirement from this API
 pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void {
-    if (symLink(allocator, existing_path, new_path)) {
+    if (symLink(existing_path, new_path)) {
         return;
     } else |err| switch (err) {
         error.PathAlreadyExists => {},
@@ -920,7 +920,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:
         try getRandomBytes(rand_buf[0..]);
         b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf);
 
-        if (symLink(allocator, existing_path, tmp_path)) {
+        if (symLink(existing_path, tmp_path)) {
             return rename(tmp_path, new_path);
         } else |err| switch (err) {
             error.PathAlreadyExists => continue,
@@ -1252,49 +1252,65 @@ pub const DeleteDirError = error{
     NotDir,
     DirNotEmpty,
     ReadOnlyFileSystem,
-    OutOfMemory,
+    InvalidUtf8,
+    BadPathName,
 
     /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
-/// Returns ::error.DirNotEmpty if the directory is not empty.
-/// To delete a directory recursively, see ::deleteTree
-pub fn deleteDir(allocator: *Allocator, dir_path: []const u8) DeleteDirError!void {
-    const path_buf = try allocator.alloc(u8, dir_path.len + 1);
-    defer allocator.free(path_buf);
+pub fn deleteDirC(dir_path: [*]const u8) DeleteDirError!void {
+    switch (builtin.os) {
+        Os.windows => {
+            const dir_path_w = try windows_util.cStrToPrefixedFileW(dir_path);
+            return deleteDirW(&dir_path_w);
+        },
+        Os.linux, Os.macosx, Os.ios => {
+            const err = posix.getErrno(posix.rmdir(dir_path));
+            switch (err) {
+                0 => return,
+                posix.EACCES => return error.AccessDenied,
+                posix.EPERM => return error.AccessDenied,
+                posix.EBUSY => return error.FileBusy,
+                posix.EFAULT => unreachable,
+                posix.EINVAL => unreachable,
+                posix.ELOOP => return error.SymLinkLoop,
+                posix.ENAMETOOLONG => return error.NameTooLong,
+                posix.ENOENT => return error.FileNotFound,
+                posix.ENOMEM => return error.SystemResources,
+                posix.ENOTDIR => return error.NotDir,
+                posix.EEXIST => return error.DirNotEmpty,
+                posix.ENOTEMPTY => return error.DirNotEmpty,
+                posix.EROFS => return error.ReadOnlyFileSystem,
+                else => return unexpectedErrorPosix(err),
+            }
+        },
+        else => @compileError("unimplemented"),
+    }
+}
 
-    mem.copy(u8, path_buf, dir_path);
-    path_buf[dir_path.len] = 0;
+pub fn deleteDirW(dir_path_w: [*]const u16) DeleteDirError!void {
+    if (windows.RemoveDirectoryW(dir_path_w) == 0) {
+        const err = windows.GetLastError();
+        switch (err) {
+            windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
+            windows.ERROR.DIR_NOT_EMPTY => return error.DirNotEmpty,
+            else => return unexpectedErrorWindows(err),
+        }
+    }
+}
 
+/// Returns ::error.DirNotEmpty if the directory is not empty.
+/// To delete a directory recursively, see ::deleteTree
+pub fn deleteDir(dir_path: []const u8) DeleteDirError!void {
     switch (builtin.os) {
         Os.windows => {
-            if (windows.RemoveDirectoryA(path_buf.ptr) == 0) {
-                const err = windows.GetLastError();
-                return switch (err) {
-                    windows.ERROR.PATH_NOT_FOUND => error.FileNotFound,
-                    windows.ERROR.DIR_NOT_EMPTY => error.DirNotEmpty,
-                    else => unexpectedErrorWindows(err),
-                };
-            }
+            const dir_path_w = try windows_util.sliceToPrefixedFileW(dir_path);
+            return deleteDirW(&dir_path_w);
         },
         Os.linux, Os.macosx, Os.ios => {
-            const err = posix.getErrno(posix.rmdir(path_buf.ptr));
-            if (err > 0) {
-                return switch (err) {
-                    posix.EACCES, posix.EPERM => error.AccessDenied,
-                    posix.EBUSY => error.FileBusy,
-                    posix.EFAULT, posix.EINVAL => unreachable,
-                    posix.ELOOP => error.SymLinkLoop,
-                    posix.ENAMETOOLONG => error.NameTooLong,
-                    posix.ENOENT => error.FileNotFound,
-                    posix.ENOMEM => error.SystemResources,
-                    posix.ENOTDIR => error.NotDir,
-                    posix.EEXIST, posix.ENOTEMPTY => error.DirNotEmpty,
-                    posix.EROFS => error.ReadOnlyFileSystem,
-                    else => unexpectedErrorPosix(err),
-                };
-            }
+            const dir_path_c = try toPosixPath(dir_path);
+            return deleteDirC(&dir_path_c);
         },
         else => @compileError("unimplemented"),
     }
@@ -1346,6 +1362,7 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!
             error.IsDir => {},
             error.AccessDenied => got_access_denied = true,
 
+            error.InvalidUtf8,
             error.SymLinkLoop,
             error.NameTooLong,
             error.SystemResources,
@@ -1353,7 +1370,6 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!
             error.NotDir,
             error.FileSystem,
             error.FileBusy,
-            error.InvalidUtf8,
             error.BadPathName,
             error.Unexpected,
             => return err,
@@ -1381,6 +1397,8 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!
                 error.NoSpaceLeft,
                 error.PathAlreadyExists,
                 error.Unexpected,
+                error.InvalidUtf8,
+                error.BadPathName,
                 => return err,
             };
             defer dir.close();
@@ -1398,7 +1416,7 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!
                 try deleteTree(allocator, full_entry_path);
             }
         }
-        return deleteDir(allocator, full_path);
+        return deleteDir(full_path);
     }
 }
 
@@ -1422,8 +1440,9 @@ pub const Dir = struct {
         },
         Os.windows => struct {
             handle: windows.HANDLE,
-            find_file_data: windows.WIN32_FIND_DATAA,
+            find_file_data: windows.WIN32_FIND_DATAW,
             first: bool,
+            name_data: [256]u8,
         },
         else => @compileError("unimplemented"),
     };
@@ -1460,6 +1479,8 @@ pub const Dir = struct {
         NoSpaceLeft,
         PathAlreadyExists,
         OutOfMemory,
+        InvalidUtf8,
+        BadPathName,
 
         /// See https://github.com/ziglang/zig/issues/1396
         Unexpected,
@@ -1471,12 +1492,13 @@ pub const Dir = struct {
             .allocator = allocator,
             .handle = switch (builtin.os) {
                 Os.windows => blk: {
-                    var find_file_data: windows.WIN32_FIND_DATAA = undefined;
-                    const handle = try windows_util.windowsFindFirstFile(allocator, dir_path, &find_file_data);
+                    var find_file_data: windows.WIN32_FIND_DATAW = undefined;
+                    const handle = try windows_util.windowsFindFirstFile(dir_path, &find_file_data);
                     break :blk Handle{
                         .handle = handle,
                         .find_file_data = find_file_data, // TODO guaranteed copy elision
                         .first = true,
+                        .name_data = undefined,
                     };
                 },
                 Os.macosx, Os.ios => Handle{
@@ -1591,9 +1613,12 @@ pub const Dir = struct {
                 if (!try windows_util.windowsFindNextFile(self.handle.handle, &self.handle.find_file_data))
                     return null;
             }
-            const name = std.cstr.toSlice(self.handle.find_file_data.cFileName[0..].ptr);
-            if (mem.eql(u8, name, ".") or mem.eql(u8, name, ".."))
+            const name_utf16le = mem.toSlice(u16, self.handle.find_file_data.cFileName[0..].ptr);
+            if (mem.eql(u16, name_utf16le, []u16{'.'}) or mem.eql(u16, name_utf16le, []u16{'.', '.'}))
                 continue;
+            // Trust that Windows gives us valid UTF-16LE
+            const name_utf8_len = std.unicode.utf16leToUtf8(self.handle.name_data[0..], name_utf16le) catch unreachable;
+            const name_utf8 = self.handle.name_data[0..name_utf8_len];
             const kind = blk: {
                 const attrs = self.handle.find_file_data.dwFileAttributes;
                 if (attrs & windows.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.Directory;
@@ -1602,7 +1627,7 @@ pub const Dir = struct {
                 break :blk Entry.Kind.Unknown;
             };
             return Entry{
-                .name = name,
+                .name = name_utf8,
                 .kind = kind,
             };
         }
@@ -2070,7 +2095,7 @@ fn testWindowsCmdLine(input_cmd_line: [*]const u8, expected_args: []const []cons
 }
 
 // TODO make this a build variable that you can set
-const unexpected_error_tracing = true;
+const unexpected_error_tracing = false;
 const UnexpectedError = error{
     /// The Operating System returned an undocumented error code.
     Unexpected,
CMakeLists.txt
@@ -581,8 +581,6 @@ set(ZIG_STD_FILES
     "os/windows/ntdll.zig"
     "os/windows/ole32.zig"
     "os/windows/shell32.zig"
-    "os/windows/shlwapi.zig"
-    "os/windows/user32.zig"
     "os/windows/util.zig"
     "os/zen.zig"
     "pdb.zig"