2016-11-22 35 views
-1

Я портировал код с C на C++ и только что нашел проблему с путями, которые содержат em-dash, например. "C: \ Temp \ тест-1.dgn". Вызов функции fstream :: open() завершится неудачно, хотя путь корректно отобразится в отладчике Visual Studio 2005.Почему не поддерживает поддержку em-тире в имени файла?

Странно, что старый код, который использовал функцию f fen() библиотеки C, отлично работает. Я подумал, что вместо этого попробую удачу с классом wfstream, а затем обнаружил, что преобразование моей строки C с использованием mbstowcs() полностью утрачивает em-dash, что означает, что он также терпит неудачу.

Я предполагаю, что это проблема локали, но почему не поддерживается em-dash в локали по умолчанию? И почему нельзя управлять потоком em-dash? Я бы подумал, что любой байт-символ, поддерживаемый файловой системой Windows, будет поддерживаться классами потоков файлов.

Учитывая эти ограничения, каков правильный способ обработки открытия потока файлов, который может содержать допустимые имена файлов Windows, которые не просто попадают на определенные символы?

+0

Прежде всего вам нужно уточнить, в каких байтах содержится ваше имя файла. Передача emdash (0x2014) в fopen будет технически работать с некоторым кастингом или разбивать его на два байта или что-то еще, но после этого он не будет emdash. Используйте, например. FindFirstFileW/FindNextFileW и т. Д., А затем посмотрите на необработанные байтовые значения. – deviantfan

+0

Извините, должен был уточнить, что у меня просто есть простая старая строчная строка стиля C, а не UTF8. Em-dash представляется как один байт - 0x97, который должен быть стандартным 8-битным символом ANSI. – Piers

ответ

0

Публикация этого решения для других, кто сталкивается с этим. Проблема в том, что Windows назначает локаль «C» при запуске по умолчанию, а em-dash (0x97) определяется в кодовой странице «Windows-1252», но не отображается в обычной таблице ASCII, используемой в локали «C». Поэтому простым решением является позвонить:

setlocale (LC_ALL, ""); 

До fstream :: открыть. Это устанавливает текущую кодовую страницу в кодовую страницу, определенную ОС. В моей программе файл, который я хотел открыть с помощью fstream, был определен пользователем, поэтому он был в системной кодовой странице (Windows-1252).

Таким образом, во время игры с unicode и широкими символами может быть решение, чтобы избежать неотображаемых символов, это не было корнем проблемы. Фактическая проблема заключалась в том, что кодовая страница входной строки («Windows-1252») не соответствовала активной кодовой странице («C»), используемой по умолчанию в программах Windows.

0

Это должно сработать при условии, что все вы делаете это в широкоугольной нотации с широкими функциями char. То есть, использование wfstream, но вместо того, чтобы использовать mbstowcs, использовать широкий строковые литералы с префиксом L полукокса:

const wchar_t* filename = L"C:\temp\test—1.dgn"; 

Кроме того, убедитесь, что ваш исходный файл будет сохранен как UTF-8 в Visual Studio. В противном случае em-dash может получить языковые проблемы с em-dash.

+0

Префикс 'L' просто добавит' 0x00'byte после друг друга.Если символ em-dash '-' закодирован как' 0x12 0x82' (маленький конец '# 8212'). 'L" - "' эквивалентно байтам '0x12 0x00 0x82 0x00 0x00 0x00', а не' 0x12 0x82 0x00 0x00'. – Gonmator

1

символы эм-тир кодируются как U+2014 в UTF-16 (0x14 0x20 в маленьких обратном порядке байт), 0xE2 0x80 0x94 в UTF-8, а также с другими кодами или нет коды вообще в зависимости от страницы кодировки и кода, используемой. Кодовая страница Windows-1252 (очень распространенная на западноевропейских языках) имеет символ тире 0x97, который мы могли бы считать эквивалентным.

Windows внутренне управляет дорожками UTF-16, поэтому каждый раз, когда функция вызывается с помощью своего плохого интерфейса ANSI (функции заканчиваются на A), путь преобразуется с использованием текущей кодовой страницы, настроенной для пользователя в UTF-16.

С другой стороны, RTL C и C++ может быть реализован при доступе к интерфейсу ANSI или Unicode (функции, заканчивающиеся на W). В первом случае кодовая страница, используемая для представления строки, должна быть одинаковой для кодовой страницы, используемой для системы. Во втором случае мы либо напрямую используем строки utf-16 с самого начала, либо функции, используемые для преобразования в utf-16, должны быть настроены на использование одной и той же кодовой страницы исходной строки для сопоставления.

Да, это сложная проблема. И есть несколько неправильно (или проблемы) предложения решить:

  • Использование wfstream вместо fstream:wfstream ничего не делать с дорожками различных по fstream. Ничего. Это просто означает «управлять потоком байтов, как wchar_t». (И он делает это по-другому, как можно ожидать, поэтому сделать этот класс непривычным в большинстве случаев, но это еще одна история). Чтобы использовать интерфейс Unicode в реализации Visual Studio, существует перегруженный конструктор и функция open(), которые принимают const wchar_t*. Эти функции и конструктор перегружены для fstream и для wfstream. Используйте fstream с правом open().
  • mbstowcs(): Проблема заключается в том, что локаль (которая содержит кодовую страницу, используемую в строке) для использования. Если вы соответствуете языку, потому что языковой стандарт по умолчанию соответствует системному, классный. Если нет, вы можете попробовать с mbstowcs_l(). Но эти функции являются небезопасными функциями C, поэтому вы должны быть осторожны с размером буфера. Во всяком случае, этот подход может иметь смысл только в том случае, если путь к конвертации получен во время выполнения. Если это статическая строка, известная во время компиляции, лучше использовать ее непосредственно в коде.
  • L"C:\\temp\\test—1.dgn":L префикс в строке не означает «преобразует эту строку в UTF-16» (код использования источника, чтобы быть в 8-битовых символов), по крайней мере, не в реализации Visual Studio. L префикс означает «добавить байт 0x00 после каждого символа между кавычками». Итак, , что эквивалентно байту 0x97 в узкой (обычной) строке, становится 0x97 0x00, когда в широком (с префиксом L) строкой, но не 0x14 0x20. Вместо этого лучше использовать свое универсальное имя персонажа: L"C:\\temp\\test\\u20141.dgn"

Один популярный подход заключается в использовании всегда в вашем коде либо UTF-8 или UTF-16 и сделать переходы только в случае крайней необходимости. При преобразовании строки с определенной кодовой страницей в utf-8 или utf-16, сначала нужно преобразовать в один из них (utf-8 или utf-16), идентифицирующий сначала правильную кодовую страницу. Для этого преобразования используются функции в зависимости от того, откуда они происходят. Если вы получите строку из XML-файла, ну, используемая кодовая страница обычно используется там (и используется для utf-8). Если это происходит из элемента управления Windows, используйте функцию Windows API, например, MultiByteToWideChar. (CP_ACP или GetACP() использует для работы по умолчанию кодовую страницу).

Использует всегда fstream (не wfstream) и его широкие интерфейсы (open и конструктор), а не его узкие. (Вы можете снова использовать MultiByteToWideChar для преобразования с utf-8 в utf-16).

Есть несколько статей и статей с советами по этому подходу. Один из них, который я вам рекомендую: http://www.nubaria.com/en/blog/?p=289.