Использование zlib с путями файлов Unicode в Windows
Я читаю сжатые файлы gzip с помощью zlib. Затем вы открываете файл с помощью
gzFile gzopen(const char *filepath, const char *mode);
Как вы обрабатываете пути к файлам Unicode, которые хранятся как const wchar_t*
в Windows?
на UNIX-подобных платформах вы можете просто преобразовать путь к файлу в UTF-8 и вызвать gzopen(), но это не будет работать на Windows.
5 ответов
следующая версия zlib будет включать в себя эту функцию, где _WIN32
is #defined:
gzFile gzopen_w(const wchar_t *path, char *mode);
это работает точно так же, как gzopen()
, за исключением того, что он использует _wopen()
вместо open()
.
Я намеренно не дублировал второй аргумент _wfopen()
, и в результате я не назвал его _wgzopen()
чтобы избежать возможной путаницы с аргументами этой функции. Отсюда и название gzopen_w()
. Это также позволяет избежать использования зарезервированного пространства имен C.
прежде всего, что is имени?
в Unix-подобных системах
имя последовательность байт завершается нулем. Ядру не нужно заботиться о кодировке символов (кроме как знать код ASCII для /
).
однако с точки зрения пользователей удобнее интерпретировать имена файлов как последовательности символы, и это делается с помощью кодировка указывается как часть языкового стандарта. поддержка Unicode путем делать UTF-8 локалей доступным.
в программах C файлы представлены с помощью обычного char*
строки функции как fopen
. нет широкозначной версии API POSIX. если у вас wchar_t*
имя файла, вы должны явно преобразовать его в char*
.
на Windows NT
имя последовательность UTF-16 кодовые единицы. На самом деле,все манипуляция строками в Windows выполняется в UTF-16 внутренне.
все библиотеки Microsoft C (++), включая библиотеку среды выполнения Visual C++, используют соглашение, которое char*
строки в локали наследие "Анси" кодовая страница, и wchar_t*
строки в UTF-16. И char*
функции - это просто обертки обратной совместимости вокруг нового wchar_t*
функции.
Итак, если вы позвоните MessageBoxA(hwnd, text, caption, type)
, это по существу то же самое, что и вызов MessageBoxW(hwnd, ToUTF16(text), ToUTF16(caption), type)
. И когда вы звоните fopen(filename, mode)
вот например _wfopen(ToUTF16(filename), ToUTF16(mode))
.
отметим, что _wfopen
один из многие нестандартные функции C для работы с wchar_t*
строки. И это не только для удобства; вы не могу использовать стандартный char*
эквивалентах потому что они ограничивают вас кодовой страницей " ANSI "(которая не может быть UTF-8). Для например, в локали windows-1252 вы не можете (легко) fopen
файл שלום.c
, потому что нет никакого способа представить эти символы в узкой строке.
в кросс-платформенной библиотеки
некоторые типичные подходы:
- используйте стандартные функции C с
char*
строки и просто не дают о поддержке символов, отличных от ANSI, в Windows. - использовать
char*
строки, но интерпретируйте их как UTF-8 вместо ANSI. На Windows, напишите функции-оболочки, которые принимают аргументы UTF-8, преобразуют их в UTF-16 и вызывают такие функции, как_wfopen
. - используйте широкие символьные строки везде, что похоже на #2, за исключением того, что вам нужно написать функции обертки для non - системы Windows.
как zlib обрабатывает имена файлов?
к сожалению, он, похоже, использует наивный подход №1 выше, с open
(а не _wopen
) используется непосредственно.
как вы можете обойти это?
кроме уже упомянутых решений (мой любимый из которых Appleman1234 в gzdopen
предложение), вы могли бы использовать символические ссылки чтобы дать файлу альтернативное имя all-ASCII, которое вы могли бы безопасно передать gzopen
. Возможно, вам даже не придется этого делать, если файл уже имеет подходящий сокращенное наименование.
у вас есть следующие варианты
#ifdef _WIN32
#define F_OPEN(name, mode) _wfopen((name), (mode))
#endif
патч zlib так, что он использует
_wfopen
в Windows, а неfopen
, используя нечто похожее на выше в zutil.hиспользовать
_wfopen
или_wopen
вместо gzopen и передайте возвращаемое значение вgzdopen
.используйте libiconv или какую-либо другую библиотеку, чтобы изменить файл enconding на ASCII из данной кодировки Unicode и передать строку ASCII в gzopen. Если libiconv не удается обработать ошибку и предложит пользователю переименовать файл.
для получения дополнительной информации о iconv см. пример iconv. В этом примере используется японский язык для UTF-8, но не было бы большого скачка, чтобы изменить кодировку назначения на ASCII или ISO 8859-1.
для получения дополнительной информации о преобразовании символов zlib и non ANSI см. здесь
вот реализация варианта № 2 Appleman. Код был протестирован.
#ifdef _WIN32
gzFile _wgzopen(const wchar_t* fileName, const wchar_t* mode)
{
FILE* stream = NULL;
gzFile gzstream = NULL;
char* cmode = NULL; // mode converted to char*
int n = -1;
stream = _wfopen(fileName, mode);
if(stream)
n = wcstombs(NULL, mode, 0);
if(n != -1)
cmode = (char*)malloc(n + 1);
if(cmode) {
wcstombs(cmode, mode, n + 1);
gzstream = gzdopen(fileno(stream), cmode);
}
free(cmode);
if(stream && !gzstream) fclose(stream);
return gzstream;
}
#endif
Я сделал и filename
и mode
const wchar_t*
для согласованности с функциями Windows, такими как
FILE* _wfopen(const wchar_t* filename, const wchar_t* mode);
вот моя собственная версия вспомогательной функции unicode, протестированная немного лучше, чем версия выше.
static void GetFlags(const char* mode, int& flags, int& pmode)
{
const char* _mode = mode;
flags = 0; // == O_RDONLY
pmode = 0; // pmode needs to be obtained, otherwise file gets read-only attribute, see
// http://stackoverflow.com/questions/1412625/why-is-the-read-only-attribute-set-sometimes-for-files-created-by-my-service
for( ; *_mode ; _mode++ )
{
switch( tolower(*_mode) )
{
case 'w':
flags |= O_CREAT | O_TRUNC;
pmode |= _S_IWRITE;
break;
case 'a':
flags |= O_CREAT | O_APPEND;
pmode |= _S_IREAD | _S_IWRITE;
break;
case 'r':
pmode |= _S_IREAD;
break;
case 'b':
flags |= O_BINARY;
break;
case '+':
flags |= O_RDWR;
pmode |= _S_IREAD | _S_IWRITE;
break;
}
}
if( (flags & O_CREAT) != 0 && (flags & O_RDWR) == 0 )
flags |= O_WRONLY;
} //GetFlags
gzFile wgzopen(const wchar_t* fileName, const char* mode)
{
gzFile gzstream = NULL;
int f = 0;
int flags = 0;
int pmode = 0;
GetFlags(mode, flags, pmode);
f = _wopen(fileName, flags, pmode );
if( f == -1 )
return NULL;
// gzdopen will also close file handle.
gzstream = gzdopen(f, mode);
if(!gzstream)
_close(f);
return gzstream;
}