Skip to content

Commit f6a0ca9

Browse files
authored
In Windows host, use WriteConsoleW for stdout and stderr, and locale enabled print for file output (#102295)
Uses WriteConsoleW for writing to stdout and std err, and uses the locale version of vfwprintf to print to a file using the utf8 locale. This properly renders utf8 and GB18030 characters in the host output.
1 parent 6575440 commit f6a0ca9

File tree

6 files changed

+92
-16
lines changed

6 files changed

+92
-16
lines changed

src/installer/tests/HostActivation.Tests/DotnetArgValidation.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
1+
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
55
using System.IO;
6-
6+
using System.Text;
77
using Microsoft.DotNet.Cli.Build;
88
using Microsoft.DotNet.TestUtils;
99
using Xunit;
@@ -95,6 +95,23 @@ public void DotNetInfo_NoSDK()
9595
.And.HaveStdOutMatching($@"RID:\s*{TestContext.BuildRID}");
9696
}
9797

98+
[Fact]
99+
public void DotNetInfo_Utf8Path()
100+
{
101+
string installLocation = Encoding.UTF8.GetString("utf8-龯蝌灋齅ㄥ䶱"u8);
102+
DotNetCli dotnet = new DotNetBuilder(sharedTestState.BaseDirectory.Location, TestContext.BuiltDotNet.BinPath, installLocation)
103+
.Build();
104+
105+
var result = dotnet.Exec("--info")
106+
.DotNetRoot(Path.Combine(sharedTestState.BaseDirectory.Location, installLocation))
107+
.CaptureStdErr()
108+
.CaptureStdOut(Encoding.UTF8)
109+
.Execute();
110+
111+
result.Should().Pass()
112+
.And.HaveStdOutMatching($@"DOTNET_ROOT.*{installLocation}");
113+
}
114+
98115
[Fact]
99116
public void DotNetInfo_WithSDK()
100117
{

src/installer/tests/TestUtils/Command.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Diagnostics;
88
using System.IO;
99
using System.Runtime.CompilerServices;
10+
using System.Text;
1011
using System.Threading;
1112

1213
namespace Microsoft.DotNet.Cli.Build.Framework
@@ -218,7 +219,7 @@ public Command Start()
218219
/// <returns>Result of the command</returns>
219220
public CommandResult WaitForExit(bool expectedToFail, int timeoutMilliseconds = Timeout.Infinite)
220221
{
221-
ReportExecWaitOnExit();
222+
ReportExecWaitOnExit();
222223

223224
int exitCode;
224225
if (!Process.WaitForExit(timeoutMilliseconds))
@@ -283,18 +284,20 @@ public Command RemoveEnvironmentVariable(string name)
283284
return this;
284285
}
285286

286-
public Command CaptureStdOut()
287+
public Command CaptureStdOut(Encoding? stdOutEncoding = null)
287288
{
288289
ThrowIfRunning();
289290
Process.StartInfo.RedirectStandardOutput = true;
291+
Process.StartInfo.StandardOutputEncoding = stdOutEncoding;
290292
_stdOutCapture = new StringWriter();
291293
return this;
292294
}
293295

294-
public Command CaptureStdErr()
296+
public Command CaptureStdErr(Encoding? stdErrEncoding = null)
295297
{
296298
ThrowIfRunning();
297299
Process.StartInfo.RedirectStandardError = true;
300+
Process.StartInfo.StandardErrorEncoding = stdErrEncoding;
298301
_stdErrCapture = new StringWriter();
299302
return this;
300303
}

src/native/corehost/apphost/apphost.windows.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ namespace
1919
// Add to buffer for later use.
2020
g_buffered_errors.append(message).append(_X("\n"));
2121
// Also write to stderr immediately
22-
pal::err_fputs(message);
22+
pal::err_print_line(message);
2323
}
2424

2525
// Determines if the current module (apphost executable) is marked as a Windows GUI application

src/native/corehost/hostmisc/pal.h

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,9 @@ namespace pal
161161

162162
inline FILE* file_open(const string_t& path, const char_t* mode) { return ::_wfsopen(path.c_str(), mode, _SH_DENYNO); }
163163

164-
inline void file_vprintf(FILE* f, const char_t* format, va_list vl) { ::vfwprintf(f, format, vl); ::fputwc(_X('\n'), f); }
165-
inline void err_fputs(const char_t* message) { ::fputws(message, stderr); ::fputwc(_X('\n'), stderr); }
166-
inline void out_vprintf(const char_t* format, va_list vl) { ::vfwprintf(stdout, format, vl); ::fputwc(_X('\n'), stdout); }
164+
void file_vprintf(FILE* f, const char_t* format, va_list vl);
165+
void err_print_line(const char_t* message);
166+
void out_vprint_line(const char_t* format, va_list vl);
167167

168168
inline int str_vprintf(char_t* buffer, size_t count, const char_t* format, va_list vl) { return ::_vsnwprintf_s(buffer, count, _TRUNCATE, format, vl); }
169169
inline int strlen_vprintf(const char_t* format, va_list vl) { return ::_vscwprintf(format, vl); }
@@ -190,7 +190,7 @@ namespace pal
190190
inline bool munmap(void* addr, size_t length) { return UnmapViewOfFile(addr) != 0; }
191191
inline int get_pid() { return GetCurrentProcessId(); }
192192
inline void sleep(uint32_t milliseconds) { Sleep(milliseconds); }
193-
#else
193+
#else // _WIN32
194194
#ifdef EXPORT_SHARED_API
195195
#define SHARED_API extern "C" __attribute__((__visibility__("default")))
196196
#else
@@ -228,8 +228,8 @@ namespace pal
228228
inline size_t strlen(const char_t* str) { return ::strlen(str); }
229229
inline FILE* file_open(const string_t& path, const char_t* mode) { return fopen(path.c_str(), mode); }
230230
inline void file_vprintf(FILE* f, const char_t* format, va_list vl) { ::vfprintf(f, format, vl); ::fputc('\n', f); }
231-
inline void err_fputs(const char_t* message) { ::fputs(message, stderr); ::fputc(_X('\n'), stderr); }
232-
inline void out_vprintf(const char_t* format, va_list vl) { ::vfprintf(stdout, format, vl); ::fputc('\n', stdout); }
231+
inline void err_print_line(const char_t* message) { ::fputs(message, stderr); ::fputc(_X('\n'), stderr); }
232+
inline void out_vprint_line(const char_t* format, va_list vl) { ::vfprintf(stdout, format, vl); ::fputc('\n', stdout); }
233233
inline int str_vprintf(char_t* str, size_t size, const char_t* format, va_list vl) { return ::vsnprintf(str, size, format, vl); }
234234
inline int strlen_vprintf(const char_t* format, va_list vl) { return ::vsnprintf(nullptr, 0, format, vl); }
235235

@@ -256,7 +256,7 @@ namespace pal
256256
inline bool munmap(void* addr, size_t length) { return ::munmap(addr, length) == 0; }
257257
inline int get_pid() { return getpid(); }
258258
inline void sleep(uint32_t milliseconds) { usleep(milliseconds * 1000); }
259-
#endif
259+
#endif // _WIN32
260260

261261
inline int snwprintf(char_t* buffer, size_t count, const char_t* format, ...)
262262
{

src/native/corehost/hostmisc/pal.windows.cpp

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,67 @@
55
#include "trace.h"
66
#include "utils.h"
77
#include "longfile.h"
8-
8+
#include <windows.h>
99
#include <cassert>
1010
#include <ShlObj.h>
1111
#include <ctime>
1212

13+
14+
void pal::file_vprintf(FILE* f, const pal::char_t* format, va_list vl)
15+
{
16+
// String functions like vfwprintf convert wide to multi-byte characters as if wcrtomb were called - that is, using the current C locale (LC_TYPE).
17+
// In order to properly print UTF-8 and GB18030 characters, we need to use the version of vfwprintf that takes a locale.
18+
_locale_t loc = _create_locale(LC_ALL, ".utf8");
19+
::_vfwprintf_l(f, format, loc, vl);
20+
::fputwc(_X('\n'), f);
21+
_free_locale(loc);
22+
}
23+
24+
namespace {
25+
void print_line_to_handle(const pal::char_t* message, HANDLE handle, FILE* fallbackFileHandle) {
26+
// String functions like vfwprintf convert wide to multi-byte characters as if wcrtomb were called - that is, using the current C locale (LC_TYPE).
27+
// In order to properly print UTF-8 and GB18030 characters to the console without requiring the user to use chcp to a compatible locale, we use WriteConsoleW.
28+
// However, WriteConsoleW will fail if the output is redirected to a file - in that case we will write to the fallbackFileHandle
29+
DWORD output;
30+
// GetConsoleMode returns FALSE when the output is redirected to a file, and we need to output to the fallback file handle.
31+
BOOL isConsoleOutput = ::GetConsoleMode(handle, &output);
32+
if (isConsoleOutput == FALSE)
33+
{
34+
// We use file_vprintf to handle UTF-8 formatting. The WriteFile api will output the bytes directly with Unicode bytes,
35+
// while pal::file_vprintf will convert the characters to UTF-8.
36+
pal::file_vprintf(fallbackFileHandle, message, va_list());
37+
}
38+
else {
39+
::WriteConsoleW(handle, message, (int)pal::strlen(message), NULL, NULL);
40+
::WriteConsoleW(handle, _X("\n"), 1, NULL, NULL);
41+
}
42+
}
43+
}
44+
45+
void pal::err_print_line(const pal::char_t* message) {
46+
// Forward to helper to handle UTF-8 formatting and redirection
47+
print_line_to_handle(message, ::GetStdHandle(STD_ERROR_HANDLE), stderr);
48+
}
49+
50+
void pal::out_vprint_line(const pal::char_t* format, va_list vl) {
51+
va_list vl_copy;
52+
va_copy(vl_copy, vl);
53+
// Get the length of the formatted string + 1 for null terminator
54+
int len = 1 + pal::strlen_vprintf(format, vl_copy);
55+
if (len < 0)
56+
{
57+
return;
58+
}
59+
std::vector<pal::char_t> buffer(len);
60+
int written = pal::str_vprintf(&buffer[0], len, format, vl);
61+
if (written != len - 1)
62+
{
63+
return;
64+
}
65+
// Forward to helper to handle UTF-8 formatting and redirection
66+
print_line_to_handle(&buffer[0], ::GetStdHandle(STD_OUTPUT_HANDLE), stdout);
67+
}
68+
1369
bool GetModuleFileNameWrapper(HMODULE hModule, pal::string_t* recv)
1470
{
1571
pal::string_t path;

src/native/corehost/hostmisc/trace.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ void trace::error(const pal::char_t* format, ...)
179179

180180
if (g_error_writer == nullptr)
181181
{
182-
pal::err_fputs(buffer.data());
182+
pal::err_print_line(buffer.data());
183183
}
184184
else
185185
{
@@ -200,7 +200,7 @@ void trace::println(const pal::char_t* format, ...)
200200
va_start(args, format);
201201
{
202202
std::lock_guard<spin_lock> lock(g_trace_lock);
203-
pal::out_vprintf(format, args);
203+
pal::out_vprint_line(format, args);
204204
}
205205
va_end(args);
206206
}

0 commit comments

Comments
 (0)