Как мне правильно использовать CharNext в Windows API?
Вопрос
У меня есть многобайтовая строка, содержащая смесь японских и латинских символов.Я пытаюсь скопировать части этой строки в отдельную ячейку памяти.Поскольку это многобайтовая строка, некоторые символы используют один байт, а другие символы - два.При копировании частей строки я не должен копировать японские символы "наполовину".Чтобы иметь возможность сделать это правильно, мне нужно иметь возможность определять, где в многобайтовой строке начинаются и заканчиваются символы.
Например, если строка содержит 3 символа, для которых требуется [2 байта] [2 байта] [1 байт], я должен скопировать либо 2, либо 4, либо 5 байт в другое место, а не 3, поскольку, если бы я копировал 3, я бы скопировал только половину второго символа.
Чтобы выяснить, где в многобайтовой строке начинаются и заканчиваются символы, я пытаюсь использовать функции Windows API CharNext и CharNextExA, но безуспешно.Когда я использую эти функции, они перемещаются по моей строке по одному байту за раз, а не по одному символу за раз.Согласно MSDN, CharNext должен Функция CharNext извлекает указатель на следующий символ в строке..
Вот некоторый код, иллюстрирующий эту проблему:
#include <windows.h>
#include <stdio.h>
#include <wchar.h>
#include <string.h>
/* string consisting of six "asian" characters */
wchar_t wcsString[] = L"\u9580\u961c\u9640\u963f\u963b\u9644";
int main()
{
// Convert the asian string from wide char to multi-byte.
LPSTR mbString = new char[1000];
WideCharToMultiByte( CP_UTF8, 0, wcsString, -1, mbString, 100, NULL, NULL);
// Count the number of characters in the string.
int characterCount = 0;
LPSTR currentCharacter = mbString;
while (*currentCharacter)
{
characterCount++;
currentCharacter = CharNextExA(CP_UTF8, currentCharacter, 0);
}
}
(пожалуйста, игнорируйте утечку памяти и неспособность выполнить проверку на ошибки.)
Теперь, в приведенном выше примере, я бы ожидал, что characterCount станет 6, поскольку это количество символов в азиатской строке.Но вместо этого characterCount становится 18, потому что mbString содержит 18 символов:
門阜陀阿阻附
Я не понимаю, как это должно работать.Как CharNext должен знать, является ли "é–€ é" в строке закодированной версией японского символа или на самом деле символами é – € и é?
Некоторые заметки:
- Я прочитал сообщение в блоге Джоэлса о том, что каждый разработчик должен знать о Unicode.Хотя, возможно, я что-то в нем неправильно понял.
- Если бы все, что я хотел сделать, это посчитать символы, я мог бы посчитать символы в азиатской строке напрямую.Имейте в виду, что моя настоящая цель - скопировать части многобайтовой строки в отдельное место.Отдельное расположение поддерживает только многобайтовый формат, а не widechar.
- Если я преобразовываю содержимое mbString обратно в wide char с помощью MultiByteToWideChar, я получаю правильную строку (門阜陀阿阻附), которая указывает, что с mbString все в порядке.
Редактировать:Очевидно, что функции CharNext не поддерживают UTF-8, но Microsoft забыла задокументировать это.Я скинул / скопировал вместе свою собственную процедуру, которую я не буду использовать и которая нуждается в улучшении.Я предполагаю, что его легко разбить.
LPSTR CharMoveNext(LPSTR szString)
{
if (szString == 0 || *szString == 0)
return 0;
if ( (szString[0] & 0x80) == 0x00)
return szString + 1;
else if ( (szString[0] & 0xE0) == 0xC0)
return szString + 2;
else if ( (szString[0] & 0xF0) == 0xE0)
return szString + 3;
else if ( (szString[0] & 0xF8) == 0xF0)
return szString + 4;
else
return szString +1;
}
Решение
Вот действительно хорошее объяснение того, что происходит здесь, в Разбираемся во всем этом блог: CharNextExA сломан?.Короче говоря, CharNext не предназначен для работы со строками UTF8.
Другие советы
Насколько я могу определить (google и эксперименты), CharNextExA
на самом деле не работает с UTF-8, поддерживаются только многобайтовые кодировки, которые используют более короткие пары байт начала / окончания или однобайтовые символы.
UTF-8 - довольно обычная кодировка, существует множество библиотек, которые будут делать то, что вы хотите, но также довольно легко создать свою собственную.
Взгляните сюда unicode.org, в частности, таблица 3-7 для допустимых форм последовательности.
const char* NextUtf8( const char* in )
{
if( in == NULL || *in == '\0' )
return in;
unsigned char uc = static_cast<unsigned char>(*in);
if( uc < 0x80 )
{
return in + 1;
}
else if( uc < 0xc2 )
{
// throw error? invalid lead byte
}
else if( uc < 0xe0 )
{
// check in[1] for validity( 0x80 .. 0xBF )
return in + 2;
}
else if( uc < 0xe1 )
{
// check in[1] for validity( 0xA0 .. 0xBF )
// check in[2] for validity( 0x80 .. 0xBF )
return in + 3;
}
else // ... etc.
// ...
}
Учитывая , что CharNextExA не работает с UTF-8, вы можете разобрать это сами.Просто пропустите символы, у которых есть 10 в двух верхних битах.Вы можете увидеть шаблон в определении UTF-8: http://en.wikipedia.org/wiki/Utf-8
LPSTR CharMoveNext(LPSTR szString)
{
++szString;
while ((*szString & 0xc0) == 0x80)
++szString;
return szString;
}
Это не прямой ответ на ваш вопрос, но вы можете найти следующий урок полезным, я, безусловно, сделал это.На самом деле представленной здесь информации достаточно, чтобы вы могли с легкостью самостоятельно просматривать многобайтовую строку:
Попробуйте использовать 932 для кодовой страницы.Я не думаю, что CP_UTF8 - это настоящая кодовая страница, и она может работать только для WideCharToMultiByte() и обратно.Вы также можете попробовать isleadByte() , но для этого требуется либо правильно установить языковой стандарт, либо правильно установить кодовую страницу по умолчанию.Я успешно использовал IsDBCSLeadByteEx(), но никогда с CP_UTF8.