4

我有转换的wchar_t*问题char*

我从结构中得到一个wchar_t*字符串,由WinAPI 函数返回,所以我假设该字符串是正确的。FILE_NOTIFY_INFORMATIONReadDirectoryChangesW

假设 wchar 字符串是“New Text File.txt” 在 Visual Studio 调试器中,当鼠标悬停在变量上时,显示“N”和一些未知的中文字母。虽然在手表中字符串表示正确。

当我尝试将 wchar 转换为 char 时wcstombs

wcstombs(pfileName, pwfileName, fileInfo.FileNameLength);

它只将两个字母转换为char*("Ne"),然后生成错误。

此块的函数 _wcstombs_l_helper() 中的 wcstombs.c 中存在一些内部错误:

if (*pwcs > 255)  /* validate high byte */
{
    errno = EILSEQ;
    return (size_t)-1;  /* error */
}

它不会作为异常抛出。

可能是什么问题?

4

3 回答 3

15

为了做你想做的事情正确的方式,你需要考虑几件重要的事情。我会尽力在这里为你分解它们。

让我们从MSDN 上函数文档count的参数定义开始:wcstombs()

多字节输出字符串中可以存储的最大字节数。

请注意,这并没有说明宽字符输入字符串中的宽字符数。即使您的示例输入字符串(“New Text File.txt”)中的所有宽字符都可以表示为单字节 ASCII 字符,但我们不能假设输入字符串中的每个宽字符都会在输出中恰好生成一个字节每个可能的输入字符串的字符串(如果此语句让您感到困惑,您应该查看Joel 关于 Unicode 和字符集的文章)。那么,如果你传递wcstombs()了输出缓冲区的大小,它怎么知道输入字符串有多长呢?该文档指出,根据标准 C 语言约定,输入字符串应为空终止:

如果 wcstombs 在count发生之前或发生时遇到宽字符空字符 (L'\0') ,它会将其转换为 8 位 0 并停止。

尽管文档中没有明确说明这一点,但我们可以推断,如果输入字符串不是以 null 结尾的,wcstombs()则将继续读取宽字符,直到将count字节写入输出字符串。因此,如果您正在处理一个非空终止的宽字符串,仅仅知道输入字符串的长度是不够的;您必须以某种方式确切地知道输出字符串需要多少字节(如果不进行转换就无法确定)并将其作为count参数传递以使其wcstombs()执行您希望它执行的操作。

为什么我如此关注这个空终止问题?因为MSDN 上FILE_NOTIFY_INFORMATION结构文档对它的领域有这样的说法FileName

包含相对于目录句柄的文件名的可变长度字段。文件名采用 Unicode 字符格式,并且不是以空值结尾的。

FileName字段不是以空值结尾的事实解释了为什么当您在调试器中查看它时它的末尾有一堆“未知的中文字母”。该结构的文档还包含有关该领域FILE_NOTIFY_INFORMATION的另一个智慧:FileNameLength

记录的文件名部分的大小,以字节为单位。

请注意,这里说的是字节,而不是字符。因此,即使您想假设输入字符串中的每个宽字符都会在输出字符串中生成一个字节,您也不应该传递fileInfo.FileNameLengthfor count; 您应该传递fileInfo.FileNameLength / sizeof(WCHAR)(当然,或者使用以 null 结尾的输入字符串)。将所有这些信息放在一起,我们终于可以理解为什么您最初的调用wcstombs()失败了:它读取到了字符串的末尾并阻塞了无效数据(从而触发了EILSEQ错误)。

现在我们已经阐明了这个问题,是时候讨论一个可能的解决方案了。为了以正确的方式做到这一点,您需要知道的第一件事是您的输出缓冲区需要多大。幸运的是,文档中有一个最后的花絮wcstombs()可以帮助我们:

如果mbstr参数为 NULL,则 wcstombs 返回目标字符串所需的大小(以字节为单位)。

因此,使用该函数的惯用方式wcstombs()是调用它两次:第一次确定输出缓冲区需要多大,第二次实际进行转换。最后要注意的是,正如我们之前所说,宽字符输入字符串至少在第一次调用wcstombs().

把这一切放在一起,这里有一段代码可以做你想做的事情:

size_t fileNameLengthInWChars = fileInfo.FileNameLength / sizeof(WCHAR); //get the length of the filename in characters
WCHAR *pwNullTerminatedFileName = new WCHAR[fileNameLengthInWChars + 1]; //allocate an intermediate buffer to hold a null-terminated version of fileInfo.FileName; +1 for null terminator
wcsncpy(pwNullTerminatedFileName, fileInfo.FileName, fileNameLengthInWChars); //copy the filename into a the intermediate buffer
pwNullTerminatedFileName[fileNameLengthInWChars] = L'\0'; //null terminate the new buffer
size_t fileNameLengthInChars = wcstombs(NULL, pwNullTerminatedFileName, 0); //first call to wcstombs() determines how long the output buffer needs to be
char *pFileName = new char[fileNameLengthInChars + 1]; //allocate the final output buffer; +1 to leave room for null terminator
wcstombs(pFileName, pwNullTerminatedFileName, fileNameLengthInChars + 1); //finally do the conversion!

delete[] pwNullTerminatedFileName当然,别忘了打电话给delete[] pFileName他们打扫卫生。

最后一件事

写完这个答案后,我更仔细地重新阅读了你的问题,并想到了你可能犯的另一个错误。您说wcstombs()仅在转换前两个字母(“Ne”)后就失败了,这意味着它在前两个宽字符之后击中了输入字符串中的未初始化数据。您是否碰巧使用赋值运算符将一个FILE_NOTIFY_INFORMATION变量复制到另一个变量?例如,

FILE_NOTIFY_INFORMATION fileInfo = someOtherFileInfo;

如果这样做,它只会复制someOtherFileInfo.FileNameto的前两个宽字符fileInfo.FileName。为了理解为什么会这样,考虑FILE_NOTIFY_INFORMATION结构的声明:

typedef struct _FILE_NOTIFY_INFORMATION {
  DWORD NextEntryOffset;
  DWORD Action;
  DWORD FileNameLength;
  WCHAR FileName[1];
} FILE_NOTIFY_INFORMATION, *PFILE_NOTIFY_INFORMATION;

FileName当编译器为赋值操作生成代码时,它不理解作为可变长度字段被拉取的诡计,所以它只是将sizeof(FILE_NOTIFY_INFORMATION)字节从复制someOtherFileInfofileInfo。由于FileName被声明为 one 的数组WCHAR,您会认为只会复制一个字符,但编译器会将 struct 填充为额外的两个字节长(因此它的长度是 an 大小的整数倍int),这这就是为什么WCHAR还要复制一秒钟的原因。

于 2011-12-24T22:44:42.563 回答
0

我的猜测是您传递的宽字符串无效或定义不正确。

如何pwFileName定义?看起来你有一个FILE_NOTIFY_INFORMATION定义为 的结构fileInfo,那么你为什么不使用fileInfo.FileName,如下所示?

wcstombs(pfileName, fileInfo.FileName, fileInfo.FileNameLength);
于 2011-12-24T22:10:49.633 回答
0

您得到的错误说明了一切,它找到了一个无法转换为 MB 的字符(因为它在 MB 中没有表示),来源

如果 wcstombs 遇到无法转换为多字节字符的宽字符,则返回 –1 强制转换为 size_t 类型并将 errno 设置为 EILSEQ

在这种情况下,您应该避免“假设”输入,并给出一个失败的实际测试用例。

于 2011-12-24T22:45:20.233 回答