Задача вот какая.
Есть кроссплатформенный проект (windows, linux), пользующийся некоторыми сторонними библиотеками.
В том числе, эти библиотеки принимают char* имена файлов, чтобы внутри себя их прочесть-записать.
Нужно как-то изловчиться, чтобы они могли работать с именами за пределами системной локали.
Например, у меня русские винды, кодировка 1251 и 866.
Беру файл с умляутами (1252), — в эксплорере он виден, в коммандкоме тоже.
fopen() обламывается.
Конечно, в виндах есть std::ifstream с конструктором (wchar_t*), а кроссплатформенно есть boost::ifstream (под виндами работает с utf-16, под никсами — с utf-8).
Но это если я на своей стороне делаю. А мне надо, чтобы сторонняя библиотека тоже смогла.
Было бы идеально взять имя в utf-8 и передать. Но библиотека же не подозревает об этом, — значит, придётся через setlocale.
И тут засада: в виндах setlocale не знает про utf-8, ни так ".utf-8", ни так "utf-8", ни даже так ".65001".
Есть какой-нибудь способ навязать utf-8 сишному рантайму? Пусть даже windows-specific?
Ладно, допустим, я ограничусь работой только с файлами, чьи имена нормально представляются в системной кодировке.
Но вот существуют модификации кодировок: 1251 русская, казахская и чувашская, 1254 турецкая и азербайджанская.
Беру, скажем, конфиг (в utf-8 или utf-16), перевожу в дефолтную кодировку штатными средствами, — имя правильно отобразится, или нет? Как он отличит, это 1254-t или 1254-a?
А потом полученную строку fopen() примет?
К сожалению, яндекс и гугл завален "детскими вопросами" — как выводить всякие кодировки на экран, или как побороть пэхапэ. Поэтому буду благодарен за точную наводку.
Перекуём баги на фичи!
Re: как подчинить библиотеку, чтоб работала с юникодом или, хотя бы, с локалью?
Здравствуйте, Кодт, Вы писали:
К>Задача вот какая. К>Есть кроссплатформенный проект (windows, linux), пользующийся некоторыми сторонними библиотеками. К>В том числе, эти библиотеки принимают char* имена файлов, чтобы внутри себя их прочесть-записать. К>Нужно как-то изловчиться, чтобы они могли работать с именами за пределами системной локали.
К>К сожалению, яндекс и гугл завален "детскими вопросами" — как выводить всякие кодировки на экран, или как побороть пэхапэ. Поэтому буду благодарен за точную наводку.
Здравствуйте, Кодт, Вы писали:
К>Задача вот какая. К>Есть кроссплатформенный проект (windows, linux), пользующийся некоторыми сторонними библиотеками. К>В том числе, эти библиотеки принимают char* имена файлов, чтобы внутри себя их прочесть-записать. К>Нужно как-то изловчиться, чтобы они могли работать с именами за пределами системной локали.
К>Например, у меня русские винды, кодировка 1251 и 866. К>Беру файл с умляутами (1252), — в эксплорере он виден, в коммандкоме тоже. К>fopen() обламывается.
Ну так в винде то всё API работает через юникод (utf-16), так что никаких проблем и не должно быть.
Далее, если мы берём стандартую библиотеку C++, то при желание работы с юникодом тоже надо брать соответствующие функции (_wfopen в данном случае), если они есть в конкретной реализации. Иначе в общем случае ничего хорошего не получится.
Естественно, если у нас в юникодном имени создаваемого файла находятся только символы входящие в системную кодировку, то можно без проблем преобразовать его в системную и передать в fopen — всё будет корректно. А вот для произвольного имени боюсь так не выйдет.
К>Было бы идеально взять имя в utf-8 и передать. Но библиотека же не подозревает об этом, — значит, придётся через setlocale. К>И тут засада: в виндах setlocale не знает про utf-8, ни так ".utf-8", ни так "utf-8", ни даже так ".65001".
К>Есть какой-нибудь способ навязать utf-8 сишному рантайму? Пусть даже windows-specific?
Сомневаюсь.
К>Ладно, допустим, я ограничусь работой только с файлами, чьи имена нормально представляются в системной кодировке. К>Но вот существуют модификации кодировок: 1251 русская, казахская и чувашская, 1254 турецкая и азербайджанская. К>Беру, скажем, конфиг (в utf-8 или utf-16), перевожу в дефолтную кодировку штатными средствами, — имя правильно отобразится, или нет? Как он отличит, это 1254-t или 1254-a? К>А потом полученную строку fopen() примет?
А какая разница то? Есть просто системная кодировка и всё. Пока в строках только символы из неё, всё будет нормально и с не юникодным API.
Re[2]: как подчинить библиотеку, чтоб работала с юникодом или, хотя бы, с локаль
Здравствуйте, Кодт, Вы писали:
К>Было бы идеально взять имя в utf-8 и передать. Но библиотека же не подозревает об этом, — значит, придётся через setlocale. К>И тут засада: в виндах setlocale не знает про utf-8, ни так ".utf-8", ни так "utf-8", ни даже так ".65001".
UTF8 и locale — перепендикулярны. Либо локаль (объяснение, как понимать 8-битные символы), либо UTF8, в которой все символы есть.
К>Есть какой-нибудь способ навязать utf-8 сишному рантайму? Пусть даже windows-specific?
Скорее всего нет, по крайней мере надежного. Дело в том, что fopen в Windows ведет на CreateFileA, куда практически без изменений передается файловый путь. А CreateFileA, похоже, использует системную локаль для интерпретации этих 8-битных символов, преобразуя их при этом в порядочные Unicode символы, которые и хранятся хоть в FAT, хоть в NTFS.
Можешь проверить. Поставь в Control Panel — Region And Language — Administrative — Language for Non-Unicode Programs немецкую локаль в качестве системной и вызови fopen с умляутами. Думаю, се пройдет, зато перестанут, скорее всего, создаваться файлы с русскими именами, либо же они окажутся немецкими .
Я пробовал перед fopen ставить немецкую локаль с помощью SetThreadLocale. Увы, не помогает. Но и точного указания, как именно CreateFileA интерпретирует в этом плане строку, я тоже не нашел. Так что все это ИМХО.
With best regards
Pavel Dvorkin
Re[2]: как подчинить библиотеку, чтоб работала с юникодом или, хотя бы, с локаль
Здравствуйте, Pavel Dvorkin, Вы писали:
К>>И тут засада: в виндах setlocale не знает про utf-8, ни так ".utf-8", ни так "utf-8", ни даже так ".65001". PD>UTF8 и locale — перепендикулярны. Либо локаль (объяснение, как понимать 8-битные символы), либо UTF8, в которой все символы есть.
Ошибаешься. Локаль — это совокупность граней (facet), описывающих разные правила — формат чисел, сравнение строк, и т.д. И в том числе — кодировку.
Только в никсах кодировка utf-8 поддерживается сишным рантаймом, а в винде — нет.
#include <clocale>
#include <cstdio>
int main()
{
puts(setlocale(LC_ALL, ""));
}
ru_RU.UTF-8, говорит мне убунту; russian_RUSSIA.1251, говорит винда
Возможно, что в винде слишком крепко и глубоко внедрено ограничение в 2 байта на символ и в 2 байта на номер кодировки.
И UTF-8 и UTF-7 поддерживаются лишь функциями MultiByteToWideChar / WideCharToMultiByte.
Да вообще, эти числовые идентификаторы LCID и CP в винде — признак кривого дизайна ещё с досовского наследства.
К>>Есть какой-нибудь способ навязать utf-8 сишному рантайму? Пусть даже windows-specific?
PD>Скорее всего нет, по крайней мере надежного. Дело в том, что fopen в Windows ведет на CreateFileA, куда практически без изменений передается файловый путь. А CreateFileA, похоже, использует системную локаль для интерпретации этих 8-битных символов, преобразуя их при этом в порядочные Unicode символы, которые и хранятся хоть в FAT, хоть в NTFS.
Надо будет как-нибудь подебажить CRT...
Я когда-то давно лазил туда, — душераздирающее зрелище. Особенно меня убило, как по-разному сделаны wprintf и wostream.
Пока что короткие досовские имена являются выходом. В их сторону и буду смотреть. Либо просто скажу заказчику "ССЗБ".
Перекуём баги на фичи!
Re[3]: как подчинить библиотеку, чтоб работала с юникодом или, хотя бы, с локаль
К>>>И тут засада: в виндах setlocale не знает про utf-8, ни так ".utf-8", ни так "utf-8", ни даже так ".65001". PD>>UTF8 и locale — перепендикулярны. Либо локаль (объяснение, как понимать 8-битные символы), либо UTF8, в которой все символы есть.
К>Ошибаешься. Локаль — это совокупность граней (facet), описывающих разные правила — формат чисел, сравнение строк, и т.д. И в том числе — кодировку.
Я имел в виду именно кодировку, естественно, а не формат даты и валюты. Хотя мог бы быть точнее, верно.
К>Только в никсах кодировка utf-8 поддерживается сишным рантаймом, а в винде — нет.
О чем, собственно, я и говорил. CreateFileA принимает строку и интерпретирует ее в соответствии с системной кодировкой (локалью)
К>Возможно, что в винде слишком крепко и глубоко внедрено ограничение в 2 байта на символ и в 2 байта на номер кодировки. К>И UTF-8 и UTF-7 поддерживаются лишь функциями MultiByteToWideChar / WideCharToMultiByte.
Видимо, да.
К>Надо будет как-нибудь подебажить CRT...
Там дебажить нечего (в плане fopen). Через несколько посредников выход на CreateFile. Путь при этом передается вроде как без изменений.
К>Пока что короткие досовские имена являются выходом. В их сторону и буду смотреть. Либо просто скажу заказчику "ССЗБ".