我正在使用zlib读取gzip压缩文件。 然后你使用以下代码打开一个文件:
gzFile gzopen(const char *filepath, const char *mode);
在Windows平台上,如果文件路径以const wchar_t*
的形式存储,该如何处理Unicode文件路径?
在类UNIX平台上,您只需将文件路径转换为UTF-8并调用gzopen()即可,但这在Windows上不起作用。
_WIN32
:
gzFile gzopen_w(const wchar_t *path, char *mode);
它的工作方式与gzopen()
相同,只是它使用_wopen()
而不是open()
。_wfopen()
的第二个参数,因此我没有将其称为_wgzopen()
,以避免可能与该函数参数混淆。 因此名称为gzopen_w()
。 这也避免了使用C保留名称空间。文件名是由零终止的一系列字节。内核不需要关心字符编码(除了知道“/”的ASCII代码)。
然而,从用户的角度来看,将文件名解释为字符序列更加方便,这通过作为本地环境的一部分指定的字符编码来完成。通过提供UTF-8本地环境来支持Unicode。
在C程序中,文件使用普通的char*
字符串表示,如fopen
函数。 POSIX API没有宽字符版本。如果您有一个wchar_t*
文件名,必须明确地将其转换为char*
。
文件名是由一系列UTF-16代码单位组成。实际上,Windows中所有的字符串操作都在内部使用UTF-16进行。
微软的C/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
是许多非标准的 C 函数之一,用于处理 wchar_t*
字符串。这不仅仅是为了方便; 你无法使用标准的 char*
等效函数,因为它们限制了你只能使用“ANSI”代码页(其中不能是 UTF-8)。例如,在 windows-1252 区域设置中,您无法(轻松地)fopen
文件 שלום.c
,因为在窄字符串中没有办法表示这些字符。
一些典型的方法包括:
char*
字符串的标准C函数,不必担心Windows上对非ANSI字符的支持。char*
字符串,但将其解释为UTF-8而不是ANSI。在Windows上,编写包装器函数以接受UTF-8参数,将它们转换为UTF-16,并调用诸如_wfopen
之类的函数。不幸的是,它似乎使用上述的天真方法#1,直接使用open
(而不是_wopen
)。
gzdopen
建议),您还可以利用符号链接给文件一个替代的全ASCII名称,然后安全地传递给gzopen
。如果文件已经有一个合适的短名称,您甚至可能不必这样做。MultiByteToWideChar()
或者 iconv()
。 - dan04 #ifdef _WIN32
#define F_OPEN(name, mode) _wfopen((name), (mode))
#endif
在Windows上使用_wfopen
替代fopen
,要打补丁到zlib中,可以参照zutil.h文件的内容。
使用_wfopen
或_wopen
替代gzopen,并将返回值传递给gzdopen
。
使用libiconv或其他库将文件编码从Unicode转换为ASCII,并将ASCII字符串传递给gzopen。如果libiconv失败,则处理错误并提示用户重命名文件。
有关iconv的更多信息,请访问iconv示例。该示例将日语转换为UTF-8,但将目标编码更改为ASCII或ISO 8859-1不会太困难。
有关zlib和非ANSI字符转换的更多信息,请参见此处。
#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
我将文件名和模式都改成了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
// https://dev59.com/20nSa4cB1Zd3GeqPM1Br
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;
}
O_BINARY
传递给 _wopen
,而不仅仅是在未压缩的数据为二进制时,否则它会破坏压缩输出中的任何 0x10! - Iziminza
char *
。(至少这是我的问题,也是我今天来这里的原因)。因此,必须有一种方法可以将wchar*
→ [lib space]char*
→fopen
→ [os space]_wfopen
,最终的 _wfopen 具有原始字符串的重构。所以问题是,dan04 的ToUTF16
的反函数是什么?是wcstombs
吗?在从我的代码到操作系统空间的链中,没有必要将字符串解释为字形,因此无法编码的字符可以保留为 mbs,并通过 ToUTF16 重构。 - v.oddou