在Windows上使用zlib和Unicode文件路径

时间:2012-03-15 09:34:23

标签: c unicode zlib

我正在使用zlib阅读gzip压缩文件。 然后使用

打开文件
gzFile gzopen(const char *filepath, const char *mode);

如何处理在Windows上存储为const wchar_t*的Unicode文件路径?

在类UNIX平台上,您只需将文件路径转换为UTF-8并调用gzopen(), 但这不适用于Windows。

5 个答案:

答案 0 :(得分:12)

zlib的下一个版本将包含此函数,其中_WIN32是#defined:

gzFile gzopen_w(const wchar_t *path, char *mode);

它的工作原理与gzopen()完全相同,只是它使用_wopen()代替open()

我故意没有复制_wfopen()的第二个参数,因此我没有将其称为_wgzopen()以避免可能与该函数的参数混淆。因此名称为gzopen_w()。这也避免了使用C保留的名称空间。

答案 1 :(得分:11)

首先,文件名?

在类Unix系统上

文件名以零结尾的字节序列。内核不需要关心字符编码(除了知道/的ASCII代码)。

但是,从用户的角度来看,将文件名解释为字符的序列会更方便,这可以通过指定为语言环境字符编码来完成>。通过使UTF-8语言环境可用来Unicode is supported

在C程序中,文件在char*等函数中用普通的 fopen字符串表示。 POSIX API没有宽字符版本。如果您有wchar_t*文件名,则必须明确将其转换为char*

在Windows NT上

文件名是一系列UTF-16代码单元。实际上,Windows中的所有字符串操作都是在内部以UTF-16完成的。

所有Microsoft的C(++)库(包括Visual C ++运行时库)都使用char*字符串在特定于语言环境的旧版“ANSI”代码页和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许多用于处理wchar_t*字符串的非标准C函数之一。这不仅仅是为了方便; 无法使用标准char*等效项,因为它们将您限制为“ANSI”代码页(can't be UTF-8)。例如,在windows-1252语言环境中,您不能(轻松地)fopen文件שלום.c,因为无法用窄字符串表示这些字符。

在跨平台库中

一些典型的方法是:

  1. 将标准C函数与char*字符串一起使用,并且不要在Windows上提供有关非ANSI字符的支持。
  2. 使用char*字符串,但将其解释为UTF-8而不是ANSI。在Windows上,编写包含UTF-8参数的包装函数,将它们转换为UTF-16,并调用_wfopen之类的函数。
  3. 在任何地方使用宽字符串,除了你需要为 -Windows系统编写包装函数外,它就像#2。
  4. zlib如何处理文件名?

    不幸的是,它似乎使用上面的天真方法#1,直接使用open(而不是_wopen)。

    你怎么解决它?

    除了已经提到的解决方案(我最喜欢的是Appleman1234的gzdopen建议),您可以利用symbolic links为文件提供一个替代的全ASCII名称,然后您可以安全地传递给该文件gzopen。如果文件已经具有合适的short name,您可能甚至不必这样做。

答案 2 :(得分:4)

您有以下选项

 #ifdef _WIN32 

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

 #endif    
  1. 修补程序zlib,以便在Windows上使用_wfopen而不是fopen,在zutil.h中使用与上面类似的内容

  2. 使用_wfopen_wopen代替gzopen,并将返回值传递给gzdopen

  3. 使用libiconv或其他库从您给定的Unicode编码更改enconding为ASCII的文件,并将ASCII字符串传递给gzopen。如果libiconv失败,则处理错误并提示用户重命名该文件。

  4. 有关iconv的更多信息,请参阅An example of iconv。该示例使用日语到UTF-8,但将目标编码更改为ASCII或ISO 8859-1并不是一个很大的飞跃。

    有关zlib和非ANSI字符转换的详细信息,请参阅here

答案 3 :(得分:3)

这是Appleman选项#2的实现。代码已经过测试。

#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

我已将 filenamemode const wchar_t*

等Windows功能保持一致
FILE* _wfopen(const wchar_t* filename, const wchar_t* mode);

答案 4 :(得分:0)

这是我自己的unicode helper函数版本,测试结果略好于上面版本。

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;
}