在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
,因为没有办法在窄字符串中表示这些字符。
在跨平台库中
一些典型的方法是:
- 将标准C函数与
char*
字符串一起使用,并且不要在Windows上给出非ANSI字符的支持。 - 使用
char*
字符串但将其解释为UTF-8而不是ANSI。 在Windows上,编写包含UTF-8参数的包装函数,将它们转换为UTF-16,并调用_wfopen
等_wfopen
。 - 在任何地方使用宽字符串,除了需要为非 Windows系统编写包装函数之外,它类似于#2。
zlib如何处理文件名?
不幸的是,它似乎使用上面的天真方法#1,直接使用open
(而不是_wopen
)。
你怎么解决这个问题?
除了已经提到的解决方案(我最喜欢的是Appleman1234的gzdopen
建议),您可以利用符号链接为文件提供一个替代的全ASCII名称,然后您可以安全地传递给gzopen
。 如果文件已经有合适的短名称 ,您甚至可能不必这样做。
您有以下选择
#ifdef _WIN32 #define F_OPEN(name, mode) _wfopen((name), (mode)) #endif
-
修补zlib,以便它在Windows上使用
_wfopen
而不是fopen
,在zutil.h中使用与上面类似的东西 -
使用
_wfopen
或_wopen
而不是gzopen,并将返回值传递给gzdopen
。 -
使用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
我已经使filename
和mode
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; }