Как правильно использовать CharNext в Windows API? - PullRequest
2 голосов
/ 06 августа 2009

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

Например, если строка содержит 3 символа, для которых требуется [2 байта] [2 байта] [1 байт], я должен скопировать 2, 4 или 5 байтов в другое место, а не 3, так как если бы я был копирование 3 Я бы скопировал только половину второго символа.

Чтобы выяснить, где в многобайтовой строке начинаются и заканчиваются символы, я пытаюсь использовать функции API-интерфейса Windows 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);
   }
}

(пожалуйста, не обращайте внимания на утечку памяти и невозможность проверки ошибок.)

Теперь в приведенном выше примере я ожидаю, что символьное число станет равным 6, поскольку это количество символов в азиатской строке. Но вместо этого CharacterCount становится 18, потому что mbString содержит 18 символов:

門阜陀阿阻附

Я не понимаю, как это должно работать. Как CharNext должен знать, является ли «é– € é» в строке кодированной версией японского символа, или на самом деле символы é - € и é?

Некоторые заметки:

  • Я прочитал в блоге Joels о том, что каждый разработчик должен знать о Unicode. Возможно, я что-то неправильно понял в этом.
  • Если бы я только хотел посчитать символы, я мог бы посчитать символы в азиатской строке напрямую. Имейте в виду, что моей настоящей целью является копирование частей многобайтовой строки в отдельное место. Отдельное расположение поддерживает только многобайтовые, а не широкие символы.
  • Если я преобразую содержимое mbString обратно в широкий символ, используя MultiByteToWideChar, я получу правильную строку (門 阜 陀 阿 阻 附), которая указывает, что с mbString все в порядке.

EDIT: Очевидно, что функции 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;
  }

Ответы [ 5 ]

4 голосов
/ 06 августа 2009

Вот действительно хорошее объяснение того, что происходит здесь, в блоге Sorting It All Out : Не работает ли CharNextExA? Короче говоря, CharNext не предназначен для работы со строками UTF8.

3 голосов
/ 06 августа 2009

Насколько я могу определить (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.
    // ...
}
2 голосов
/ 06 августа 2009

Учитывая, что 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;
}
0 голосов
/ 06 августа 2009

Попробуйте использовать 932 для кодовой страницы. Я не думаю, что CP_UTF8 - настоящая кодовая страница, и она может работать только для WideCharToMultibyte () и обратно. Вы также можете попробовать isleadByte (), но это требует либо правильной установки локали, либо правильной настройки кодовой страницы по умолчанию. Я успешно использовал IsDBCSLeadByteEx (), но никогда не использовал CP_UTF8.

0 голосов
/ 06 августа 2009

Это не прямой ответ на ваш вопрос, но вы, возможно, сочтете полезным следующее руководство. На самом деле, приведенной здесь информации достаточно, чтобы вы могли легко пройти многобайтовую строку:

Полное руководство по строкам

...