wchar_t * to char * проблемы с конвертацией - PullRequest
4 голосов
/ 25 декабря 2011

У меня проблема с преобразованием wchar_t* в char*.

Я получаю строку wchar_t* из структуры FILE_NOTIFY_INFORMATION, возвращенную функцией ReadDirectoryChangesW WinAPI, поэтому я предполагаю, что строка правильная.

Предположим, что строка wchar имеет значение "New Text File.txt" В Visual Studio отладчик при наведении на переменную в показывает «N» и некоторые неизвестные китайские буквы. Хотя в часах строка представлена ​​правильно.

Когда я пытаюсь преобразовать wchar в char с помощью wcstombs

wcstombs(pfileName, pwfileName, fileInfo.FileNameLength);

он преобразует только две буквы в char* ("Ne") и затем генерирует ошибку.

Некоторая внутренняя ошибка в wcstombs.c в функции _wcstombs_l_helper () в этом блоке:

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

Это не исключение.

В чем может быть проблема?

Ответы [ 3 ]

14 голосов
/ 25 декабря 2011

Чтобы сделать то, что вы пытаетесь сделать Правильным путем, есть несколько нетривиальных вещей, которые вы должны принять во внимание. Я сделаю все возможное, чтобы сломать их для вас здесь.

Давайте начнем с определения параметра 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.

0 голосов
/ 25 декабря 2011

ошибка, которую вы получаете, говорит само за себя, он обнаружил символ, который не может быть преобразован в МБ (потому что он не имеет представления в МБ), источник :

Если wcstombs встречает широкий символ, он не может преобразоваться в многобайтовый символ, возвращает -1 приведение к типу size_t и устанавливает errno в EILSEQ

В подобных случаях вы должны избегать «предполагаемых» вводов и приводить фактический тестовый случай, который не проходит.

0 голосов
/ 25 декабря 2011

Я предполагаю, что передаваемая вами широкая строка неверна или неправильно определена.

Как определяется pwFileName? Кажется, у вас есть структура FILE_NOTIFY_INFORMATION, определенная как fileInfo, так почему вы не используете fileInfo.FileName, как показано ниже?

wcstombs(pfileName, fileInfo.FileName, fileInfo.FileNameLength);
...