Use Win32 APIs for UTF-16<->UTF-8 conversion

std::codecvt is deprecated since C++17 and broken for some characters/locales
This commit is contained in:
Cameron Gutman
2024-02-11 15:16:41 -06:00
parent 8689469ea8
commit 69a3edd9b0
7 changed files with 113 additions and 41 deletions

View File

@@ -7,8 +7,6 @@
#include <mmdeviceapi.h> #include <mmdeviceapi.h>
#include <roapi.h> #include <roapi.h>
#include <codecvt>
#include <synchapi.h> #include <synchapi.h>
#include <newdev.h> #include <newdev.h>
@@ -19,6 +17,8 @@
#include "src/logging.h" #include "src/logging.h"
#include "src/platform/common.h" #include "src/platform/common.h"
#include "misc.h"
// Must be the last included file // Must be the last included file
// clang-format off // clang-format off
#include "PolicyConfig.h" #include "PolicyConfig.h"
@@ -89,7 +89,6 @@ namespace platf::audio {
PROPVARIANT prop; PROPVARIANT prop;
}; };
static std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
struct format_t { struct format_t {
enum type_e : int { enum type_e : int {
none, none,
@@ -613,7 +612,7 @@ namespace platf::audio {
audio::wstring_t wstring; audio::wstring_t wstring;
device->GetId(&wstring); device->GetId(&wstring);
sink.host = converter.to_bytes(wstring.get()); sink.host = to_utf8(wstring.get());
collection_t collection; collection_t collection;
auto status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &collection); auto status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &collection);
@@ -627,7 +626,7 @@ namespace platf::audio {
collection->GetCount(&count); collection->GetCount(&count);
// If the sink isn't a device name, we'll assume it's a device ID // 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; auto virtual_device_found = false;
for (auto x = 0; x < count; ++x) { for (auto x = 0; x < count; ++x) {
@@ -674,7 +673,7 @@ namespace platf::audio {
} }
if (virtual_device_found) { 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 { 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::stereo - 1].name) + name_suffix,
"virtual-"s.append(formats[format_t::surr51 - 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); auto sink_info = get_sink_info(sink);
// If the sink isn't a device name, we'll assume it's a device ID // 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) { if (sink_info.first == format_t::none) {
// wstring_device_id does not contain virtual-(format name) // wstring_device_id does not contain virtual-(format name)
@@ -839,7 +838,7 @@ namespace platf::audio {
UINT count; UINT count;
collection->GetCount(&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) { for (auto x = 0; x < count; ++x) {
audio::device_t device; audio::device_t device;

View File

@@ -3,7 +3,6 @@
* @brief todo * @brief todo
*/ */
#include <cmath> #include <cmath>
#include <codecvt>
#include <initguid.h> #include <initguid.h>
#include <thread> #include <thread>
@@ -447,10 +446,8 @@ namespace platf::dxgi {
return -1; return -1;
} }
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter; auto adapter_name = from_utf8(config::video.adapter_name);
auto output_name = from_utf8(display_name);
auto adapter_name = converter.from_bytes(config::video.adapter_name);
auto output_name = converter.from_bytes(display_name);
adapter_t::pointer adapter_p; adapter_t::pointer adapter_p;
for (int tries = 0; tries < 2; ++tries) { for (int tries = 0; tries < 2; ++tries) {
@@ -561,7 +558,7 @@ namespace platf::dxgi {
DXGI_ADAPTER_DESC adapter_desc; DXGI_ADAPTER_DESC adapter_desc;
adapter->GetDesc(&adapter_desc); adapter->GetDesc(&adapter_desc);
auto description = converter.to_bytes(adapter_desc.Description); auto description = to_utf8(adapter_desc.Description);
BOOST_LOG(info) BOOST_LOG(info)
<< std::endl << std::endl
<< "Device Description : " << description << std::endl << "Device Description : " << description << std::endl
@@ -1066,8 +1063,6 @@ namespace platf {
BOOST_LOG(debug) << "Detecting monitors..."sv; BOOST_LOG(debug) << "Detecting monitors..."sv;
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
// We must set the GPU preference before calling any DXGI APIs! // We must set the GPU preference before calling any DXGI APIs!
if (!dxgi::probe_for_gpu_preference(config::video.output_name)) { if (!dxgi::probe_for_gpu_preference(config::video.output_name)) {
BOOST_LOG(warning) << "Failed to set GPU preference. Capture may not work!"sv; BOOST_LOG(warning) << "Failed to set GPU preference. Capture may not work!"sv;
@@ -1088,7 +1083,7 @@ namespace platf {
BOOST_LOG(debug) BOOST_LOG(debug)
<< std::endl << std::endl
<< "====== ADAPTER ====="sv << 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 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 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 << "Device Video Mem : "sv << adapter_desc.DedicatedVideoMemory / 1048576 << " MiB"sv << std::endl
@@ -1104,7 +1099,7 @@ namespace platf {
DXGI_OUTPUT_DESC desc; DXGI_OUTPUT_DESC desc;
output->GetDesc(&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 width = desc.DesktopCoordinates.right - desc.DesktopCoordinates.left;
auto height = desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top; auto height = desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top;

View File

@@ -4,8 +4,6 @@
*/ */
#include <cmath> #include <cmath>
#include <codecvt>
#include <d3dcompiler.h> #include <d3dcompiler.h>
#include <directxmath.h> #include <directxmath.h>
@@ -342,9 +340,8 @@ namespace platf::dxgi {
#ifndef NDEBUG #ifndef NDEBUG
flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#endif #endif
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, 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); auto status = D3DCompileFromFile(wFile.c_str(), nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE, entrypoint, shader_model, flags, 0, &compiled_p, &msg_p);
if (msg_p) { if (msg_p) {

View File

@@ -2,7 +2,6 @@
* @file src/platform/windows/misc.cpp * @file src/platform/windows/misc.cpp
* @brief todo * @brief todo
*/ */
#include <codecvt>
#include <csignal> #include <csignal>
#include <filesystem> #include <filesystem>
#include <iomanip> #include <iomanip>
@@ -35,6 +34,8 @@
#define NTDDI_VERSION NTDDI_WIN10 #define NTDDI_VERSION NTDDI_WIN10
#include <Shlwapi.h> #include <Shlwapi.h>
#include "misc.h"
#include "src/entry_handler.h" #include "src/entry_handler.h"
#include "src/globals.h" #include "src/globals.h"
#include "src/logging.h" #include "src/logging.h"
@@ -66,8 +67,6 @@ using namespace std::literals;
namespace platf { namespace platf {
using adapteraddrs_t = util::c_ptr<IP_ADAPTER_ADDRESSES>; using adapteraddrs_t = util::c_ptr<IP_ADAPTER_ADDRESSES>;
static std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
bool enabled_mouse_keys = false; bool enabled_mouse_keys = false;
MOUSEKEYS previous_mouse_keys_state; MOUSEKEYS previous_mouse_keys_state;
@@ -297,7 +296,7 @@ namespace platf {
// Parse the environment block and populate env // Parse the environment block and populate env
for (auto c = (PWCHAR) env_block; *c != UNICODE_NULL; c += wcslen(c) + 1) { 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. // 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_name = env_tuple.substr(0, env_tuple.find('='));
std::string env_val = env_tuple.substr(env_tuple.find('=') + 1); std::string env_val = env_tuple.substr(env_tuple.find('=') + 1);
@@ -371,7 +370,7 @@ namespace platf {
for (const auto &entry : env) { for (const auto &entry : env) {
auto name = entry.get_name(); auto name = entry.get_name();
auto value = entry.to_string(); 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' */; size += 1 /* L'\0' */;
@@ -383,9 +382,9 @@ namespace platf {
auto value = entry.to_string(); auto value = entry.to_string();
// Construct the NAME=VAL\0 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'='; 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'; env_block[offset++] = L'\0';
} }
@@ -625,14 +624,14 @@ namespace platf {
*/ */
std::wstring std::wstring
resolve_command_string(const std::string &raw_cmd, const std::wstring &working_dir, HANDLE token) { 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 // 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); auto raw_cmd_parts = boost::program_options::split_winmain(raw_cmd_w);
if (raw_cmd_parts.empty()) { if (raw_cmd_parts.empty()) {
// This is highly unexpected, but we'll just return the raw string and hope for the best. // 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; 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); 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); res = UrlGetPartW(raw_target.c_str(), scheme.data(), &out_len, URL_PART_SCHEME, 0);
if (res != S_OK) { if (res != S_OK) {
BOOST_LOG(warning) << "Failed to extract URL scheme from URL: "sv << raw_target << " ["sv << util::hex(res).to_string_view() << ']'; 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 ':') // 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 (extension == nullptr || *extension == 0) {
// If the file has no extension, assume it's a command and allow CreateProcess() // If the file has no extension, assume it's a command and allow CreateProcess()
// to try to find it via PATH // 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) // 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 // Override HKEY_CLASSES_ROOT and HKEY_CURRENT_USER to ensure we query the correct class info
if (!override_per_user_predefined_keys(token)) { 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 // Find the command string for the specified class
@@ -699,7 +698,7 @@ namespace platf {
if (res != S_OK) { 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() << ']'; 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(). // Finally, construct the real command string that will be passed into CreateProcess().
@@ -825,7 +824,7 @@ namespace platf {
*/ */
bp::child 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) { 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; HANDLE job = group ? group->native_handle() : nullptr;
STARTUPINFOEXW startup_info = create_startup_info(file, job ? &job : nullptr, ec); STARTUPINFOEXW startup_info = create_startup_info(file, job ? &job : nullptr, ec);
PROCESS_INFORMATION process_info; PROCESS_INFORMATION process_info;
@@ -1602,4 +1601,70 @@ namespace platf {
} }
return {}; 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 } // namespace platf

View File

@@ -20,4 +20,20 @@ namespace platf {
std::chrono::nanoseconds std::chrono::nanoseconds
qpc_time_difference(int64_t performance_counter1, int64_t performance_counter2); 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 } // namespace platf

View File

@@ -107,12 +107,10 @@ namespace platf::publish {
service(bool enable, PDNS_SERVICE_INSTANCE &existing_instance) { service(bool enable, PDNS_SERVICE_INSTANCE &existing_instance) {
auto alarm = safe::make_alarm<PDNS_SERVICE_INSTANCE>(); auto alarm = safe::make_alarm<PDNS_SERVICE_INSTANCE>();
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
std::wstring name { SERVICE_INSTANCE_NAME.data(), SERVICE_INSTANCE_NAME.size() }; std::wstring name { SERVICE_INSTANCE_NAME.data(), SERVICE_INSTANCE_NAME.size() };
std::wstring domain { SERVICE_TYPE_DOMAIN.data(), SERVICE_TYPE_DOMAIN.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 {}; DNS_SERVICE_INSTANCE instance {};
instance.pszInstanceName = name.data(); instance.pszInstanceName = name.data();

View File

@@ -29,6 +29,9 @@
#include "utility.h" #include "utility.h"
#ifdef _WIN32 #ifdef _WIN32
// from_utf8() string conversion function
#include "platform/windows/misc.h"
// _SH constants for _wfsopen() // _SH constants for _wfsopen()
#include <share.h> #include <share.h>
#endif #endif
@@ -187,8 +190,7 @@ namespace proc {
#ifdef _WIN32 #ifdef _WIN32
// fopen() interprets the filename as an ANSI string on Windows, so we must convert it // 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. // to UTF-16 and use the wchar_t variants for proper Unicode log file path support.
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter; auto woutput = platf::from_utf8(_app.output);
auto woutput = converter.from_bytes(_app.output);
// Use _SH_DENYNO to allow us to open this log file again for writing even if it is // 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 // still open from a previous execution. This is required to handle the case of a