Я столкнулся с проблемой при попытке получить все имена файлов из каталога. Проблема возникает при обработке определенных строк, что приводит к ошибкам. Ниже приведен фрагмент кода:
#include <filesystem>
int main()
{
const char* dir = "D:\\Music";
std::vector<std::string> musicList;
for (const auto& entry : std::filesystem::recursive_directory_iterator(dir))
{
if (entry.is_regular_file())
{
musicList.emplace_back(entry.path().string());
}
}
}
Проблема возникает в entry.path().string()
при обработке строк типа L"D:\\Music\\suki\\Angel Note - 月明かりは優しく・・・.mp3"
. Программа завершается с ошибкой, указывающей на:
_STD_BEGIN
// We would really love to use the proper way of building error_code by specializing
// is_error_code_enum and make_error_code for __std_win_error, but because:
// 1. We would like to keep the definition of __std_win_error in xfilesystem_abi.h
// 2. and xfilesystem_abi.h cannot include <system_error>
// 3. and specialization of is_error_code_enum and overload of make_error_code
// need to be kept together with the enum (see limerick in N4950 [temp.expl.spec]/8)
// we resort to using this _Make_ec helper.
_NODISCARD inline error_code _Make_ec(__std_win_error _Errno) noexcept { // make an error_code
return { static_cast<int>(_Errno), _STD system_category() };
}
[[noreturn]] inline void _Throw_system_error_from_std_win_error(const __std_win_error _Errno) {
_THROW(system_error{ _Make_ec(_Errno) }); // Here occur error!
}
_STD_END
Я скомпилировал код в Visual Studio 2022, стандарт C++ — C++17.
После расследования я упростил проблему:
#include <filesystem>
int main()
{
std::filesystem::path path = L"・";
auto str = path.string();
}
Аналогичные проблемы возникли в path.string()
. После дальнейшего упрощения с помощью L"\u30FB"
я обнаружил, что символ ・
представлен как "\u30FB"
.
Хотя path.wstring()
, path.u8string()
и другие преобразования строк работают хорошо, мне нужен char*
для таких API, как ImGui::Text(str)
или API FMOD. Попытки преобразовать wstring
в string
с помощью codecvt
, Win32 API или ICU приводили к искаженному тексту, например "・"
:
#include <filesystem>
#include <Windows.h>
std::string ws2s(const std::wstring& wstr)
{
int len = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr);
std::string str;
str.reserve(len);
WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, str.data(), len, nullptr, nullptr);
return str;
}
int main()
{
std::filesystem::path path = L"\u30FB";
auto str = ws2s(path.wstring());
}
В результате строка была "・"
вместо "\u30FB"
.
Есть ли надежный способ эффективно справиться с этой ситуацией?
Хорошо, я нашел проблему. Это кодировка, используемая в интерфейсе отладки VS. Он неправильно отображает UTF-8. Например, содержимое моего vector<string>
имеет формат UTF-8, но интерфейс отладки показывает искаженный текст, как и содержимое vec[0]
. Все, что мне нужно сделать, это добавить ,s8
к vec[0]
в окне просмотра. Это заставляет дисплей отладки правильно отображать содержимое UTF-8.
О, Microsoft, почему ты настаиваешь на UTF-16? Разве UTF-8 недостаточно хорош?
Да, ImGui хочет UTF-8. Обязательно загружайте в шрифты правильные глифы, поскольку по умолчанию это не так.
Когда вы проверяете std::string
, который, по вашему мнению, содержит UTF-8, не проверяйте его, распечатывая, это часто печатается неправильно, особенно в Windows. Вместо этого посмотрите на фактические байты (скажем, в отладчике) и посмотрите, являются ли они ожидаемой кодировкой UTF-8.
Вы говорите, что текст «искажен», но это именно то, что я ожидал увидеть для представления символа, отличного от ASCII, в UTF-8, отображаемого на стандартной кодовой странице Windows.
«Мне нужен char*
для таких API, как ImGui::Text(str)
» — вы можете использовать u8string().c_str()
, чтобы получить кодировку UTF-8 char*
. Кстати, у вашей функции ws2s()
есть утечка памяти. Вам необходимо delete[]
массив char[]
, который вы выделяете, прежде чем return
использовать std::string
. Лучше всего вообще не выделять char[]
, вместо этого вы можете заранее определить размер std::string
, а затем преобразовать его непосредственно в его буфер памяти, используя неконстантный std::string::data()
метод.
@Алан Бертлз std::filesystem::path path(L"\u30FB"); авто u8str = path.u8string(); ImGui::Text(u8str.c_str()); Я получаю искаженные символы в u8str и отображаю '?' в ИмГуи. Возможно, мне нужно включить соответствующий ttf в ImGui::IO, но u8str по-прежнему показывает искаженные символы (в отладчике).
@john О, вообще-то во время отладки я обнаружил, что результат path.string() искажен
@Remy Lebeau Да, u8string работает хорошо, но проблема в том, как мне преобразовать char8_t* в char* для использования в API? О, спасибо, что напомнили мне о функции ws2s. На самом деле это было создано ChatGPT, и я не присматривался. Это действительно недостаточно строго, хаха.
@guruguru достаточно простого приведения типов от char8_t*
к char*
.
@Remy Lebeau Действительно, я пробовал const char8_t* ch8 = u8"\u1234"; символ* ch = (char*)ch8; и это сработало хорошо. std::u8string u8str = u8"\u1234"; std::string str = (char*)u8str.c_str(); тоже нормально работало вот так.
@guruguru, вам следует использовать reinterpret_cast
вместо приведения C-style
(тем более, что вы отбрасываете константность!), например: const char* ch = reinterpret_cast<const char*>(ch8);
. Но в любом случае в последнем случае я бы использовал либо std::string str(reinterpret_cast<const char*>(u8str.c_str()), u8str.size());
, либо std::string str(u8str.begin(), u8str.end());
. Хотя на самом деле вам вообще не нужен std::string
: std::u8string u8str = u8"\u1234"; const char* ch = reinterpret_cast<const char*>(u8str.c_str());
@Remy Лебо Действительно, это правда. Но могу ли я использовать const char* ch = std::bit_cast<const char*>(ch8);
?
@guruguru У меня нет опыта работы с bit_cast
, поэтому я не могу на этот вопрос ответить.
Программа аварийно завершает работу, поскольку std::filesystem::path::string
выдает исключение, а ваш код его не перехватывает.
Это проблема с кодировкой. Добавьте это в начало вашей программы, и проблема должна быть решена:
static constexpr char localeName[] = "ja_JP.utf-8";
// Instruct the C standard library that Japanese will be used with UTF-8 encoding
std::setlocale(LC_ALL, localeName);
// Instruct the C++ standard library that Japanese will be used with UTF-8 encoding, for example in std::string, std::ostream
std::locale::global(std::locale(localeName));
// Use the system locale (language and encoding) when printing data to std::cout
// Note that if your system is using a different encoding than UTF-8, like CP932, the C++ standard library will implicitly do a conversion.
std::cout.imbue(std::locale{""});
У меня была аналогичная проблема с boost::filesystem::path
, и это решило проблему.
Обратите внимание, что часть кодирования является наиболее важной. В MSVC это также должно решить проблему:
static constexpr char localeName[] = ".utf-8";
Вот полная демонстрация с этим кодом:
#include <iostream>
#include <filesystem>
#include <locale>
#define LOG(x) std::cout << #x " = " << x << '\n'
int main()
{
std::locale::global(std::locale{".utf-8"});
// use system encoding - language neutral
std::locale sysLoc{std::locale{"C"}, "", std::locale::ctype};
std::cout.imbue(sysLoc);
std::cerr.imbue(sysLoc);
for (const auto& dir_en : std::filesystem::directory_iterator{"."})
{
LOG(dir_en);
LOG(dir_en.path());
LOG(dir_en.path().string());
std::cout << "---------------\n";
}
}
Я получил следующие результаты:
C:\Users\marekR22\Downloads\MyDir>dir
Volume in drive C has no label.
Volume Serial Number is 5608-EF1A
Directory of C:\Users\marekR22\Downloads\MyDir
07/16/2024 03:58 PM <DIR> .
07/16/2024 03:58 PM <DIR> ..
07/16/2024 03:56 PM 47 Angel Note - 月明かりは優しく・・・.txt
07/16/2024 03:55 PM 526 main.cpp
2 File(s) 573 bytes
2 Dir(s) 7,074,545,664 bytes free
C:\Users\marekR22\Downloads\MyDir>cl /std:c++20 /EHcs /O2 /D NDEBUG /utf-8 main.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.39.33523 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
main.cpp
Microsoft (R) Incremental Linker Version 14.39.33523.0
Copyright (C) Microsoft Corporation. All rights reserved.
/out:main.exe
main.obj
C:\Users\marekR22\Downloads\MyDir>chcp
Active code page: 852
C:\Users\marekR22\Downloads\MyDir>main.exe
dir_en = ".\\Angel Note - ????????.txt"
dir_en.path() = ".\\Angel Note - ????????.txt"
dir_en.path().string() = .\Angel Note - ????????.txt
---------------
dir_en = ".\\main.cpp"
dir_en.path() = ".\\main.cpp"
dir_en.path().string() = .\main.cpp
---------------
dir_en = ".\\main.exe"
dir_en.path() = ".\\main.exe"
dir_en.path().string() = .\main.exe
---------------
dir_en = ".\\main.obj"
dir_en.path() = ".\\main.obj"
dir_en.path().string() = .\main.obj
---------------
C:\Users\marekR22\Downloads\MyDir>chcp 65001
Active code page: 65001
C:\Users\marekR22\Downloads\MyDir>main.exe
dir_en = ".\\Angel Note - 月明かりは優しく・・・.txt"
dir_en.path() = ".\\Angel Note - 月明かりは優しく・・・.txt"
dir_en.path().string() = .\Angel Note - 月明かりは優しく・・・.txt
---------------
dir_en = ".\\main.cpp"
dir_en.path() = ".\\main.cpp"
dir_en.path().string() = .\main.cpp
---------------
dir_en = ".\\main.exe"
dir_en.path() = ".\\main.exe"
dir_en.path().string() = .\main.exe
---------------
dir_en = ".\\main.obj"
dir_en.path() = ".\\main.obj"
dir_en.path().string() = .\main.obj
---------------
C:\Users\marekR22\Downloads\MyDir>chcp 932
Active code page: 932
C:\Users\marekR22\Downloads\MyDir>main.exe
dir_en = ".\\Angel Note - 月明かりは優しく・・・.txt"
dir_en.path() = ".\\Angel Note - 月明かりは優しく・・・.txt"
dir_en.path().string() = .\Angel Note - 月明かりは優しく・・・.txt
---------------
dir_en = ".\\main.cpp"
dir_en.path() = ".\\main.cpp"
dir_en.path().string() = .\main.cpp
---------------
dir_en = ".\\main.exe"
dir_en.path() = ".\\main.exe"
dir_en.path().string() = .\main.exe
---------------
dir_en = ".\\main.obj"
dir_en.path() = ".\\main.obj"
dir_en.path().string() = .\main.obj
---------------
Обратите внимание: когда моя кодовая страница не поддерживает японские символы, печатается ?
(без сбоев). После того, как я изменил кодовую страницу на 65001 (которая представляет UTF-8), печатаются правильные японские символы. Он также отлично работает, когда используется японская кодовая страница 932.
Спасибо за ваш ответ. Я попробовал ваш код, и он работает очень хорошо, не вызывая никаких исключений. Однако есть еще небольшая проблема. Когда я проверяю строку, преобразованную из path.string(), в отладчике, я обнаруживаю, что она все еще искажена, хотя символы в пути отображаются правильно.
Кроме того, у меня есть еще один вопрос. Я использовал японские символы, поэтому мне нужно установить ja_JP.utf-8. Что, если строка содержит несколько языков, например корейский или другие? Кроме того, некоторые имена файлов могут содержать символы, отличные от UTF-8, например, закодированные в GBK. Как мне с этим справиться? Как правильно обработать текстовый набор, содержащий несколько кодировок символов?
Важной частью является .utf-8
, который определяет кодировку, способную охватить все возможные символы Юникода, поэтому она должна работать для всех языков. В вашем случае ja_JP
часть не важна. Это влияет на такие вещи, как лексикографическая сортировка, печать чисел или валюты, дата и так далее.
В Windows с MSVC static constexpr char localeName[] = ".utf-8";
будет работать, но в системах POSIX этот локаль не будет правильно проанализирован (к сожалению, существует разница в названии локали между платформами).
Этот подход может решить проблему только кроссплатформенности, но в настоящее время меня не беспокоят платформы, отличные от Windows. Возможно, я мог бы использовать Win32 для решения этой проблемы, но я не знаком с Win32, и способ, которым C++ решает эту проблему, прост. На данный момент небольшая проблема заключается в том, что path.string() приводит к искажению текста в среде отладки, хотя я еще не пробовал выводить его результаты, поэтому не уверен, окажет ли это какое-либо влияние.
На всякий случай я упомянул другие платформы. Просто попробуйте, я уверен, что это сработает.
Все в порядке. попробую сделать.
@guruguru смотрите обновление ответа. Я запускаю этот код на своей машине.
О, я получил тот же результат на своем компьютере. Он справляется вполне хорошо. На самом деле это просто проблема с кодировкой консоли. Установка UTF-8 обеспечивает правильное отображение, а использование setlocale предотвращает исключения во время выполнения. Я попробовал применить ваше решение с помощью API FMOD, который передал искаженный текст (я проверил значения в отладчике), но он все равно работал нормально (воспроизводя музыку). Я подозреваю, что это может быть проблема с кодировкой на странице отладки VS, вызывающая искаженное отображение содержимого отладки, тогда как использование wstring отображает его правильно. И на самом деле лучше использовать UTF-16 для передачи параметров в Win API.
Какая кодировка ожидается
ImGui::Text
? Поддерживает ли он вообще символы, отличные от ascii? Если он ожидает utf-8, почему бы не просто использовать path.u8string()?