Использование 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, потому что нет никакого способа представить эти символы в узкой строке.

в кросс-платформенной библиотеки

некоторые типичные подходы:

  1. используйте стандартные функции C с char* строки и просто не дают о поддержке символов, отличных от ANSI, в Windows.
  2. использовать char* строки, но интерпретируйте их как UTF-8 вместо ANSI. На Windows, напишите функции-оболочки, которые принимают аргументы UTF-8, преобразуют их в UTF-16 и вызывают такие функции, как _wfopen.
  3. используйте широкие символьные строки везде, что похоже на #2, за исключением того, что вам нужно написать функции обертки для non - системы Windows.

как zlib обрабатывает имена файлов?

к сожалению, он, похоже, использует наивный подход №1 выше, с open (а не _wopen) используется непосредственно.

как вы можете обойти это?

кроме уже упомянутых решений (мой любимый из которых Appleman1234 в gzdopen предложение), вы могли бы использовать символические ссылки чтобы дать файлу альтернативное имя all-ASCII, которое вы могли бы безопасно передать gzopen. Возможно, вам даже не придется этого делать, если файл уже имеет подходящий сокращенное наименование.


у вас есть следующие варианты

 #ifdef _WIN32 

 #define F_OPEN(name, mode) _wfopen((name), (mode))

 #endif    
  1. патч zlib так, что он использует _wfopen в Windows, а не fopen , используя нечто похожее на выше в zutil.h

  2. использовать _wfopen или _wopen вместо gzopen и передайте возвращаемое значение в gzdopen.

  3. используйте 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;
}