Q: переносимые имена файлов в локальных кодировках (C++)

А вот, извиняюсь, вопрос.

Вот есть имя файла в национальной кодировке и я его хочу fopen(). На Винде и на Маке одним куском кода (хе-хе).

Насколько я сумел это изучить, ситуация такая:

  • Win32: или я отдаю в fopen() 8-битную кодировку (1251), или в _wfopen() в wchar_t (UCS-16?)
  • Mac: отдаем в fopen() UTF-8 и нам щастье
  • Linux: не знаю, пока руки не дошли.
Но это все с русским, который представим в виде 8-бит. А с китайским? Сдается мне, что в винде это только через _wfopen() получится.

Вопрос: есть какой-то совместимый способ, одинаковый на всех помянутых системах, или так и придется #ifdef WIN32...?

Comments

В Linux можно уже считать что "отдаем в utf-8 и будет счастье".
Уже несколько версий как все дистрибутивы по умолчанию ставят локаль utf-8.
А GNOME начал навязывать utf-8 как кодировку файловой системы еще несколько раньше.
Так что сейчас про фокусы с переменной среды G_FILENAME_ENCODING уже можно забыть.
Тем более что за пределами Gtk-based GUI-шных программ никто на эту переменную особо и не смотрел.
В Win32 если очень извратиться можно выставить utf-8 (aka code page 65001) в качестве "национальной" кодировки.

65001 - не выход, пользователя не заставишь это сделать.
То есть получается две ветки кода, одна с wchar_t, вторая с UTF8, так что ли?

Не надо заставлять это делать ПОЛЬЗОВАТЕЛЯ. Сильно извратиться - значит сделать это внутри программы.
Хотя, похоже проще сделать свою обертку вокруг fopen, которая будет сначала звать MultiByteToWideChar с явным указанием 65001, а потом уже _wfopen.
И #ifdef _WIN32 останется во одном include.

Вопрос мой, на самом деле, распадается на два

1) Что делать с библиотекой? Я подозреваю, что для Win32 нужно кроме open_file(const char*fname) дать еще open_wfile(const wchar_t *), а на остальных системах - не давать.

2) Что делать с каким-то собственным приложением для end-user. Так как там имена файлов исходно юникодные (потому что Qt's QString), проблем с преобразованием вроде бы нет

А что, в Qt еще своей обертки для открытия файлов, получающей QString нет? Я думал что там уже это, совсем для всего обертки есть. Если уж они свой database abstraction layer написали...

А в библиотеке для win32 нужно еще бы TCHAR поддержать. Который в зависимости от не помню уже каких дефайнов будет либо char, либо wchar_t.

В Qt все есть, но что мне с их файлов проку?

Если есть все, то должен быть и fileno

Да, он есть, но в том месте, куда передается filename (LibRaw) - нельзя передать дескриптор.

И делать такой интерфейс, с дескриптором, не хочется, по куче причин. Передадут туда сокет (в котором нету seek) и что с ним делать?

Что с ним делать понятно. На то в errno.h есть EBADF. А как его возвращать вызывающему - ну так же как возвррается EIO.

Но в общем я именно за то и не люблю Qt и им подобные, что они крайне плохо стыкуются со всем что должно поддерживтаь еще что-то кроме них.

Не, к сожалению функциональности нужно больше, чем есть у сокета. Например, нужно уметь открыть JPEG2000 stream (jasper-ом). Ну и там еще немного.

То есть это можно делать над буфером (и делается) или над char *filename, а с просто int filehandle - не получается.

Посему - от Qt мне нужно только имя файла. И там для этого все есть, оно в std::wstring умеет сконвертировать. Проблема лишь в том, что в виндах нужно одно (wstring), а в остальных системах - другое (utf8 в char*)

Да вероятнее всего utf8 и все будет хорошо.
Но я когда совсем недавно столкнулся с этим вопросом делал конверсию так:
На всех платформах принимаю имя utf8 encoded. Конвертирую в wchar_t (на windows это получается utf16, на Linux нормальные 32bit ISO10646), все строковые операции в wchar_t
Далее на win _wfopen(), на linux делаю конверсию в установленную пользователем locale
То есть вероятнее всего в обратно в utf8. Но если у кого-то koi8-r, то нормально.

Но надо помнить, что не всегда по полученому имени файла можно будет восстановить оригинальное имя в Unicode.
Использовать code page 65001 оказалось проблематично, подробности я забыл но их помнит google.

Попадалась мне некоторое время назад статья, как разные файловые системы воспринимают различным образом написанные уникодные имена (в смысле, символ или комбинация символа с диакритиками).

Да-да, про разницу в виде UTF в Mac/Linux я уже прочитал в исходниках Qt (QFile), это тоже мне доставило.

Под Windoze в общем случае только _wfopen() потому что 8-битовая кодировка зависит от локали.
Впрочем, перекодировать из UTF8 довольно просто, так что один из возможных подходов - решить, что библиотека работает только в UTF8, и уже внутри перекодировать как надо.
Можно, впрочем, и в обратную сторону примерно так же.

Вроде в unix-ах (в mac видимо аналогично) стандартные ф-ии не работают с wchar, поэтому там мультибайтный UTF-8. Казалось бы в Windows надо просто выбрать utf8 как специальную кодовую страницу, но похоже так нельзя, так можно только при конвертации. В итоге надо использовать wchar.

http://stackoverflow.com/questions/166503/utf-8-in-windows

Вообще-то под Виндой "безобидный" char*, прилетающий от системы (через GUIшные контролы или через параметры командной строки) - это вовсе не восьмибитовые ACSII-строки, как можно было бы ожидать, а строки в так называемой multi-byte кодировке.
Для русского языка этот MBCS вырождается в ASCII, так как одного байта вполне хватает для таблицы cp1251, а вот во всяких японских и китайских локалях один символ на экране соответствует двум и более char'ам в строке, которую получит приложение.

Ну и сама система работает с этим зоопарком в согласованном стиле, то есть если мы получили на вход (то есть от пользователя) строку в MBCS, то можем вполне передавать эту строку далее, во всякие там CreateFile и прочие функции. Соответственно, можно ожидать, что Cшный fopen нормально отреагирует на аргумент в MBCS (с MS VC это точно так, насчет других компиляторов нужно проверять).

Всё это, естественно, только в том случае, если по-китайски вводят имя файла в системе с установленным системным китайским языком.
Для русской системы китайская MBCS-строку - полная чушь и наоборот.
То есть если вдруг понадобится работать сразу с несколькими языками, то придется всё делать только через wchar, тут уж без вариантов.

Подождите, у винды есть два fopen:
fopen(const char *path,...)
_wfopen(const wchar_t *path,...)

И мне казалось, что multibyte char передается во вторую, иначе зачем бы она вдруг была нужна.

У Винды есть CreateFileA и CreateFileW.
Первая работает с обнобайтовыми строками в кодировке MBCS (то есть со строками, каждый символ которой занимает 1 или 2 байта; по идее может быть и большее количество, но в MSDN указывается, что символы длинее 2х байтов не поддерживаются), вторая с честными двух-байтовыми юникодными строками (каждый символ которых всегда занимает ровно 2 байта).

C-шные fopen и _wfopen внутри используют именно CreateFileA и CreateFileW, соответственно.

Более подробная информация по этим ссылкам:
http://msdn.microsoft.com/en-us/library/5z097dxa.aspx
http://msdn.microsoft.com/en-us/library/cwe8bzh0.aspx

Очень хорошо про разницу MBCS и юникода расписано тут:
http://www.codeproject.com/KB/string/cppstringguide1.aspx

В некотором смысле, виндовый MBCS - это аналог UTF8, так как тоже имеются специальные управляющие коды и разные символы кодируются различным количеством байтов.

Грамотно сделанные сайты "решают это" легко: спрашиваем - отвечают.
Что мешает при установке/перезапуске спросить систему: "На якой мови гутарим?"

1) Я не понял, причем тут сайты
2) Вопрос вообще не про это. А вот про что:

Вот у меня есть имя файла, полученное через Qt File Selection Dialog, лежит в QString в виде Unicode (в каком-то там представлении). Есть ли способ это преобразовать так, чтобы fopen() удовлетворился бы этим преобразованием на *любой* системе (Win, Mac, Linux)

Знание языка/кодовой страницы для этого, по идее, вовсе не нужно.

ИМХО, способ простой: ДО инициализации функционала либы спросить систему о кодировке. Либо средствами самой либы, либо положить её в "оболочку" - при обращении к либе "оболочка" либы спрашивает систему о кодировке и передаёт "параметры" в либу.

Ага, я - воинствующий профан. Согласен заранее!

Да, это не дело либы - разбираться в осях и кодировках, но если нужен универсальность и работоспособность функционала..., смиритесь и положите "это" внутрь.

Это как проблема выбора статической либо динамической линковки.

PS. я сам всё про себя знаю. :-)

Да кодировку я, допустим, знаю.

Вопрос то в другом:
- в mac/linux можно давать в fopen просто utf-8
- в винде - нельзя

Поэтому суть вопроса в том, есть ли какой-то способ, который одинаково работает во всех трех системах. Похоже, нету, значит код надо писать с #ifdef