Чтобы сделать то, что вы пытаетесь сделать Правильным путем, есть несколько нетривиальных вещей, которые вы должны принять во внимание. Я сделаю все возможное, чтобы сломать их для вас здесь.
Давайте начнем с определения параметра count
из документации функции wcstombs()
в MSDN :
Максимальное количество байтов, которое можно сохранить в многобайтовой выходной строке.
Обратите внимание, что это НЕ говорит о количестве широких символов в строке ввода широких символов. Даже если все широкие символы в вашей входной строке примера («New Text File.txt») могут быть представлены как однобайтовые символы ASCII, мы не можем предполагать, что каждый широкий символ во входной строке будет генерировать ровно один байт в выходных данных. строка для каждой возможной входной строки (если это утверждение смущает вас, вы должны прочитать статью Джоэла о Unicode и наборах символов ). Итак, если вы передадите wcstombs()
размер выходного буфера, как он узнает, какова длина входной строки? В документации говорится, что входная строка должна заканчиваться нулем в соответствии со стандартным соглашением языка C:
Если wcstombs встречает пустой символ широких символов (L '\ 0') либо до, либо при count , он преобразует его в 8-битный 0 и останавливается.
Хотя это явно не указано в документации, мы можем сделать вывод, что если входная строка не заканчивается нулем, wcstombs()
будет продолжать читать широкие символы до тех пор, пока не будет записано count
байт в выходную строку. Поэтому, если вы имеете дело с строкой широких символов, которая не заканчивается нулем, недостаточно просто знать, какова длина входной строки; вам нужно как-то точно знать, сколько байтов потребуется для выходной строки (что невозможно определить без выполнения преобразования), и передать его в качестве параметра count
, чтобы wcstombs()
сделал то, что вы хотите.
Почему я так много внимания уделяю этой проблеме нулевого завершения? Потому что документация структуры FILE_NOTIFY_INFORMATION
на MSDN говорит о своем поле FileName
:
Поле переменной длины, которое содержит имя файла относительно дескриптора каталога. Имя файла в символьном формате Юникод и не заканчивается нулем.
Тот факт, что поле FileName
не оканчивается нулем, объясняет, почему в конце есть группа «неизвестных китайских букв», когда вы смотрите на это в отладчике. Документация структуры FILE_NOTIFY_INFORMATION
также содержит еще один кусочек мудрости относительно поля FileNameLength
:
Размер части имени файла записи в байтах.
Обратите внимание, что здесь написано байтов , а не символов . Поэтому, даже если вы хотите предположить, что каждый широкий символ во входной строке будет генерировать ровно один байт в выходной строке, вы не должны передавать fileInfo.FileNameLength
для count
; Вы должны передать fileInfo.FileNameLength / sizeof(WCHAR)
(или, конечно, использовать нулевую входную строку). Собрав всю эту информацию вместе, мы, наконец, можем понять, почему ваш исходный вызов 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
в 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
объявлен как массив из одного WCHAR
, вы можете подумать, что будет скопирован только один символ, но компилятор добавит в структуру дополнительные два байта (так что его длина будет целочисленным кратнымразмер int
), поэтому также копируется секунда WCHAR
.