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

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

gzFile gzopen(const char *filepath, const char *mode); 

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

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

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

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

它的工作方式与gzopen()完全相同,只是它使用_wopen()而不是open()

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

首先,什么文件名?

在类Unix系统上

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

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

在C程序中,文件用fopen函数中的普通char*字符串表示。 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”代码页( 不能是UTF-8 )。 例如,在windows-1252语言环境中,你不能(轻松地) fopen文件שלום.c ,因为没有办法在窄字符串中表示这些字符。

在跨平台库中

一些典型的方法是:

  1. 将标准C函数与char*字符串一起使用,并且不要在Windows上给出非ANSI字符的支持。
  2. 使用char*字符串但将其解释为UTF-8而不是ANSI。 在Windows上,编写包含UTF-8参数的包装函数,将它们转换为UTF-16,并调用_wfopen_wfopen
  3. 在任何地方使用宽字符串,除了需要为非 Windows系统编写包装函数之外,它类似于#2。

zlib如何处理文件名?

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

你怎么解决这个问题?

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

您有以下选择

  #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失败,则处理错误并提示用户重命名该文件。

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

有关zlib和非ANSI字符转换的更多信息,请参见此处

这是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等function保持一致

 FILE* _wfopen(const wchar_t* filename, const wchar_t* mode); 

这是我自己的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; }