From 69a3edd9b01c76aa44fd5c2a29de1c3b3722cb41 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 11 Feb 2024 15:16:41 -0600 Subject: [PATCH] Use Win32 APIs for UTF-16<->UTF-8 conversion std::codecvt is deprecated since C++17 and broken for some characters/locales --- src/platform/windows/audio.cpp | 15 ++--- src/platform/windows/display_base.cpp | 15 ++--- src/platform/windows/display_vram.cpp | 5 +- src/platform/windows/misc.cpp | 93 +++++++++++++++++++++++---- src/platform/windows/misc.h | 16 +++++ src/platform/windows/publish.cpp | 4 +- src/process.cpp | 6 +- 7 files changed, 113 insertions(+), 41 deletions(-) diff --git a/src/platform/windows/audio.cpp b/src/platform/windows/audio.cpp index 296112e1ad3..3e9267b32aa 100644 --- a/src/platform/windows/audio.cpp +++ b/src/platform/windows/audio.cpp @@ -7,8 +7,6 @@ #include #include -#include - #include #include @@ -19,6 +17,8 @@ #include "src/logging.h" #include "src/platform/common.h" +#include "misc.h" + // Must be the last included file // clang-format off #include "PolicyConfig.h" @@ -89,7 +89,6 @@ namespace platf::audio { PROPVARIANT prop; }; - static std::wstring_convert, wchar_t> converter; struct format_t { enum type_e : int { none, @@ -613,7 +612,7 @@ namespace platf::audio { audio::wstring_t wstring; device->GetId(&wstring); - sink.host = converter.to_bytes(wstring.get()); + sink.host = to_utf8(wstring.get()); collection_t collection; auto status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &collection); @@ -627,7 +626,7 @@ namespace platf::audio { collection->GetCount(&count); // If the sink isn't a device name, we'll assume it's a device ID - auto virtual_device_id = find_device_id_by_name(config::audio.virtual_sink).value_or(converter.from_bytes(config::audio.virtual_sink)); + auto virtual_device_id = find_device_id_by_name(config::audio.virtual_sink).value_or(from_utf8(config::audio.virtual_sink)); auto virtual_device_found = false; for (auto x = 0; x < count; ++x) { @@ -674,7 +673,7 @@ namespace platf::audio { } if (virtual_device_found) { - auto name_suffix = converter.to_bytes(virtual_device_id); + auto name_suffix = to_utf8(virtual_device_id); sink.null = std::make_optional(sink_t::null_t { "virtual-"s.append(formats[format_t::stereo - 1].name) + name_suffix, "virtual-"s.append(formats[format_t::surr51 - 1].name) + name_suffix, @@ -749,7 +748,7 @@ namespace platf::audio { auto sink_info = get_sink_info(sink); // If the sink isn't a device name, we'll assume it's a device ID - auto wstring_device_id = find_device_id_by_name(sink).value_or(converter.from_bytes(sink_info.second.data())); + auto wstring_device_id = find_device_id_by_name(sink).value_or(from_utf8(sink_info.second.data())); if (sink_info.first == format_t::none) { // wstring_device_id does not contain virtual-(format name) @@ -839,7 +838,7 @@ namespace platf::audio { UINT count; collection->GetCount(&count); - auto wstring_name = converter.from_bytes(name.data()); + auto wstring_name = from_utf8(name.data()); for (auto x = 0; x < count; ++x) { audio::device_t device; diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index 78c927e7782..a5f8cc5d31a 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -3,7 +3,6 @@ * @brief todo */ #include -#include #include #include @@ -447,10 +446,8 @@ namespace platf::dxgi { return -1; } - std::wstring_convert, wchar_t> converter; - - auto adapter_name = converter.from_bytes(config::video.adapter_name); - auto output_name = converter.from_bytes(display_name); + auto adapter_name = from_utf8(config::video.adapter_name); + auto output_name = from_utf8(display_name); adapter_t::pointer adapter_p; for (int tries = 0; tries < 2; ++tries) { @@ -561,7 +558,7 @@ namespace platf::dxgi { DXGI_ADAPTER_DESC adapter_desc; adapter->GetDesc(&adapter_desc); - auto description = converter.to_bytes(adapter_desc.Description); + auto description = to_utf8(adapter_desc.Description); BOOST_LOG(info) << std::endl << "Device Description : " << description << std::endl @@ -1066,8 +1063,6 @@ namespace platf { BOOST_LOG(debug) << "Detecting monitors..."sv; - std::wstring_convert, wchar_t> converter; - // We must set the GPU preference before calling any DXGI APIs! if (!dxgi::probe_for_gpu_preference(config::video.output_name)) { BOOST_LOG(warning) << "Failed to set GPU preference. Capture may not work!"sv; @@ -1088,7 +1083,7 @@ namespace platf { BOOST_LOG(debug) << std::endl << "====== ADAPTER ====="sv << std::endl - << "Device Name : "sv << converter.to_bytes(adapter_desc.Description) << std::endl + << "Device Name : "sv << to_utf8(adapter_desc.Description) << std::endl << "Device Vendor ID : 0x"sv << util::hex(adapter_desc.VendorId).to_string_view() << std::endl << "Device Device ID : 0x"sv << util::hex(adapter_desc.DeviceId).to_string_view() << std::endl << "Device Video Mem : "sv << adapter_desc.DedicatedVideoMemory / 1048576 << " MiB"sv << std::endl @@ -1104,7 +1099,7 @@ namespace platf { DXGI_OUTPUT_DESC desc; output->GetDesc(&desc); - auto device_name = converter.to_bytes(desc.DeviceName); + auto device_name = to_utf8(desc.DeviceName); auto width = desc.DesktopCoordinates.right - desc.DesktopCoordinates.left; auto height = desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top; diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp index 74b0bab7fbf..e9f8140d525 100644 --- a/src/platform/windows/display_vram.cpp +++ b/src/platform/windows/display_vram.cpp @@ -4,8 +4,6 @@ */ #include -#include - #include #include @@ -342,9 +340,8 @@ namespace platf::dxgi { #ifndef NDEBUG flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; #endif - std::wstring_convert, wchar_t> converter; - auto wFile = converter.from_bytes(file); + auto wFile = from_utf8(file); auto status = D3DCompileFromFile(wFile.c_str(), nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE, entrypoint, shader_model, flags, 0, &compiled_p, &msg_p); if (msg_p) { diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index 708fd267484..2014d9435b8 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -2,7 +2,6 @@ * @file src/platform/windows/misc.cpp * @brief todo */ -#include #include #include #include @@ -35,6 +34,8 @@ #define NTDDI_VERSION NTDDI_WIN10 #include +#include "misc.h" + #include "src/entry_handler.h" #include "src/globals.h" #include "src/logging.h" @@ -66,8 +67,6 @@ using namespace std::literals; namespace platf { using adapteraddrs_t = util::c_ptr; - static std::wstring_convert, wchar_t> converter; - bool enabled_mouse_keys = false; MOUSEKEYS previous_mouse_keys_state; @@ -297,7 +296,7 @@ namespace platf { // Parse the environment block and populate env for (auto c = (PWCHAR) env_block; *c != UNICODE_NULL; c += wcslen(c) + 1) { // Environment variable entries end with a null-terminator, so std::wstring() will get an entire entry. - std::string env_tuple = converter.to_bytes(std::wstring { c }); + std::string env_tuple = to_utf8(std::wstring { c }); std::string env_name = env_tuple.substr(0, env_tuple.find('=')); std::string env_val = env_tuple.substr(env_tuple.find('=') + 1); @@ -371,7 +370,7 @@ namespace platf { for (const auto &entry : env) { auto name = entry.get_name(); auto value = entry.to_string(); - size += converter.from_bytes(name).length() + 1 /* L'=' */ + converter.from_bytes(value).length() + 1 /* L'\0' */; + size += from_utf8(name).length() + 1 /* L'=' */ + from_utf8(value).length() + 1 /* L'\0' */; } size += 1 /* L'\0' */; @@ -383,9 +382,9 @@ namespace platf { auto value = entry.to_string(); // Construct the NAME=VAL\0 string - append_string_to_environment_block(env_block, offset, converter.from_bytes(name)); + append_string_to_environment_block(env_block, offset, from_utf8(name)); env_block[offset++] = L'='; - append_string_to_environment_block(env_block, offset, converter.from_bytes(value)); + append_string_to_environment_block(env_block, offset, from_utf8(value)); env_block[offset++] = L'\0'; } @@ -625,14 +624,14 @@ namespace platf { */ std::wstring resolve_command_string(const std::string &raw_cmd, const std::wstring &working_dir, HANDLE token) { - std::wstring raw_cmd_w = converter.from_bytes(raw_cmd); + std::wstring raw_cmd_w = from_utf8(raw_cmd); // First, convert the given command into parts so we can get the executable/file/URL without parameters auto raw_cmd_parts = boost::program_options::split_winmain(raw_cmd_w); if (raw_cmd_parts.empty()) { // This is highly unexpected, but we'll just return the raw string and hope for the best. BOOST_LOG(warning) << "Failed to split command string: "sv << raw_cmd; - return converter.from_bytes(raw_cmd); + return from_utf8(raw_cmd); } auto raw_target = raw_cmd_parts.at(0); @@ -646,7 +645,7 @@ namespace platf { res = UrlGetPartW(raw_target.c_str(), scheme.data(), &out_len, URL_PART_SCHEME, 0); if (res != S_OK) { BOOST_LOG(warning) << "Failed to extract URL scheme from URL: "sv << raw_target << " ["sv << util::hex(res).to_string_view() << ']'; - return converter.from_bytes(raw_cmd); + return from_utf8(raw_cmd); } // If the target is a URL, the class is found using the URL scheme (prior to and not including the ':') @@ -658,7 +657,7 @@ namespace platf { if (extension == nullptr || *extension == 0) { // If the file has no extension, assume it's a command and allow CreateProcess() // to try to find it via PATH - return converter.from_bytes(raw_cmd); + return from_utf8(raw_cmd); } // For regular files, the class is found using the file extension (including the dot) @@ -674,7 +673,7 @@ namespace platf { // Override HKEY_CLASSES_ROOT and HKEY_CURRENT_USER to ensure we query the correct class info if (!override_per_user_predefined_keys(token)) { - return converter.from_bytes(raw_cmd); + return from_utf8(raw_cmd); } // Find the command string for the specified class @@ -699,7 +698,7 @@ namespace platf { if (res != S_OK) { BOOST_LOG(warning) << "Failed to query command string for raw command: "sv << raw_cmd << " ["sv << util::hex(res).to_string_view() << ']'; - return converter.from_bytes(raw_cmd); + return from_utf8(raw_cmd); } // Finally, construct the real command string that will be passed into CreateProcess(). @@ -825,7 +824,7 @@ namespace platf { */ bp::child run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, const bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) { - std::wstring start_dir = converter.from_bytes(working_dir.string()); + std::wstring start_dir = from_utf8(working_dir.string()); HANDLE job = group ? group->native_handle() : nullptr; STARTUPINFOEXW startup_info = create_startup_info(file, job ? &job : nullptr, ec); PROCESS_INFORMATION process_info; @@ -1602,4 +1601,70 @@ namespace platf { } return {}; } + + /** + * @brief Converts a UTF-8 string into a UTF-16 wide string. + * @param string The UTF-8 string. + * @return The converted UTF-16 wide string. + */ + std::wstring + from_utf8(const std::string &string) { + // No conversion needed if the string is empty + if (string.empty()) { + return {}; + } + + // Get the output size required to store the string + auto output_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, string.data(), string.size(), nullptr, 0); + if (output_size == 0) { + auto winerr = GetLastError(); + BOOST_LOG(error) << "Failed to get UTF-16 buffer size: "sv << winerr; + return {}; + } + + // Perform the conversion + std::wstring output(output_size, L'\0'); + output_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, string.data(), string.size(), output.data(), output.size()); + if (output_size == 0) { + auto winerr = GetLastError(); + BOOST_LOG(error) << "Failed to convert string to UTF-16: "sv << winerr; + return {}; + } + + return output; + } + + /** + * @brief Converts a UTF-16 wide string into a UTF-8 string. + * @param string The UTF-16 wide string. + * @return The converted UTF-8 string. + */ + std::string + to_utf8(const std::wstring &string) { + // No conversion needed if the string is empty + if (string.empty()) { + return {}; + } + + // Get the output size required to store the string + auto output_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), string.size(), + nullptr, 0, nullptr, nullptr); + if (output_size == 0) { + auto winerr = GetLastError(); + BOOST_LOG(error) << "Failed to get UTF-8 buffer size: "sv << winerr; + return {}; + } + + // Perform the conversion + std::string output(output_size, '\0'); + output_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), string.size(), + output.data(), output.size(), nullptr, nullptr); + if (output_size == 0) { + auto winerr = GetLastError(); + BOOST_LOG(error) << "Failed to convert string to UTF-8: "sv << winerr; + return {}; + } + + return output; + } } // namespace platf diff --git a/src/platform/windows/misc.h b/src/platform/windows/misc.h index 9228ce59fe7..5a3e29b0257 100644 --- a/src/platform/windows/misc.h +++ b/src/platform/windows/misc.h @@ -20,4 +20,20 @@ namespace platf { std::chrono::nanoseconds qpc_time_difference(int64_t performance_counter1, int64_t performance_counter2); + + /** + * @brief Converts a UTF-8 string into a UTF-16 wide string. + * @param string The UTF-8 string. + * @return The converted UTF-16 wide string. + */ + std::wstring + from_utf8(const std::string &string); + + /** + * @brief Converts a UTF-16 wide string into a UTF-8 string. + * @param string The UTF-16 wide string. + * @return The converted UTF-8 string. + */ + std::string + to_utf8(const std::wstring &string); } // namespace platf diff --git a/src/platform/windows/publish.cpp b/src/platform/windows/publish.cpp index 6eb4d8948e1..131bb5ac11f 100644 --- a/src/platform/windows/publish.cpp +++ b/src/platform/windows/publish.cpp @@ -107,12 +107,10 @@ namespace platf::publish { service(bool enable, PDNS_SERVICE_INSTANCE &existing_instance) { auto alarm = safe::make_alarm(); - std::wstring_convert, wchar_t> converter; - std::wstring name { SERVICE_INSTANCE_NAME.data(), SERVICE_INSTANCE_NAME.size() }; std::wstring domain { SERVICE_TYPE_DOMAIN.data(), SERVICE_TYPE_DOMAIN.size() }; - auto host = converter.from_bytes(boost::asio::ip::host_name() + ".local"); + auto host = from_utf8(boost::asio::ip::host_name() + ".local"); DNS_SERVICE_INSTANCE instance {}; instance.pszInstanceName = name.data(); diff --git a/src/process.cpp b/src/process.cpp index e660e8162a1..804291577c2 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -29,6 +29,9 @@ #include "utility.h" #ifdef _WIN32 + // from_utf8() string conversion function + #include "platform/windows/misc.h" + // _SH constants for _wfsopen() #include #endif @@ -187,8 +190,7 @@ namespace proc { #ifdef _WIN32 // fopen() interprets the filename as an ANSI string on Windows, so we must convert it // to UTF-16 and use the wchar_t variants for proper Unicode log file path support. - std::wstring_convert, wchar_t> converter; - auto woutput = converter.from_bytes(_app.output); + auto woutput = platf::from_utf8(_app.output); // Use _SH_DENYNO to allow us to open this log file again for writing even if it is // still open from a previous execution. This is required to handle the case of a