为了做你想做的事情正确的方式,你需要考虑几件重要的事情。我会尽力在这里为你分解它们。
让我们从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.FileNameLength
for 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.FileName
to的前两个宽字符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)
字节从复制someOtherFileInfo
到fileInfo
。由于FileName
被声明为 one 的数组WCHAR
,您会认为只会复制一个字符,但编译器会将 struct 填充为额外的两个字节长(因此它的长度是 an 大小的整数倍int
),这这就是为什么WCHAR
还要复制一秒钟的原因。