- Универсальный декодер — конвертер кириллицы
- Результат
- О программе
- Использование
- Ограничения
- Условия использования
- Переводчики
- Что нового
- Convert unicode to windows
- Конвертер текста в юникод
- Что такое Юникод?
- Кодовое пространство
- C++ — Преобразования Unicode-кодировок с помощью STL-строк и Win32 API
- От абстрактных кодовых точек к реальным битам: кодировки UTF-8 и UTF-16
- Преобразования между UTF-8 и UTF-16 через Win32 API
- Преобразование из UTF-8 в UTF-16: MultiByteToWideChar в действии
- Библиотека преобразований Unicode-кодировок
- Заключение
Универсальный декодер — конвертер кириллицы
Результат
[Результат перекодировки появится здесь. ]
Поставьте ссылку на наш сайт!
Универсальный декодер кириллицы
For a small fee I can help you quickly recode/recover large pieces of data — texts, databases, websites. or write custom functions you can use (invoice available).
FAQ and contact information.
О программе
Здравствуйте! Эта страница может пригодиться, если вам прислали текст (предположительно на кириллице), который отображается в виде странной комбинации загадочных символов. Программа попытается угадать кодировку, а если не получится, покажет примеры всех комбинаций кодировок, чтобы вы могли выбрать подходящую.
Использование
- Скопируйте текст в большое текстовое поле дешифратора. Несколько первых слов будут проанализированы, поэтому желательно, чтобы в них содержалась (закодированная) кириллица.
- Программа попытается декодировать текст и выведет результат в нижнее поле.
- В случае удачной перекодировки вы увидите текст в кириллице, который можно при необходимости скопировать и сохранить.
- В случае неудачной перекодировки (текст не в кириллице, состоящий из тех же или других нечитаемых символов) можно выбрать из нового выпадающего списка вариант в кириллице (если их несколько, выбирайте самый длинный). Нажав OK вы получите корректный перекодированный текст.
- Если текст перекодирован лишь частично, попробуйте выбрать другие варианты кириллицы из выпадающего списка.
Ограничения
- Если текст состоит из вопросительных знаков («. ?? . «), то проблема скорее всего на стороне отправителя и восстановить текст не получится. Попросите отправителя послать текст заново, желательно в формате простого текстового файла или в документе LibreOffice/OpenOffice/MSOffice.
- Не любой текст может быть гарантированно декодирован, даже если есть вы уверены на 100%, что он написан в кириллице.
- Анализируемый и декодированный тексты ограничены размером в 100 Кб.
- Программа не всегда дает стопроцентную точность: при перекодировке из одной кодовой страницы в другую могут пропасть некоторые символы, такие как болгарские кавычки, реже отдельные буквы и т.п.
- Программа проверяет максимум 7245 вариантов из двух и трех перекодировок: если имело место многократное перекодирование вроде koi8(utf(cp1251(utf))), оно не будет распознано или проверено. Обычно возможные и отображаемые верные варианты находятся между 32 и 255.
- Если части текста закодированы в разных кодировках, программа сможет распознать только одну часть за раз.
Условия использования
Пожалуйста, обратите внимание на то, что данная бесплатная программа создана с надеждой, что она будет полезна, но без каких-либо явных или косвенных гарантий пригодности для любого практического использования. Вы можете пользоваться ей на свой страх и риск.
Если вы используете для перекодировки очень длинный текст, убедитесь, что имеется его резервная копия.
Переводчики
Страница подготовки переводов на другие языки находится тут.
Что нового
October 2013 : I am trying different optimizations for the system which should make the decoder run faster and handle more text. If you notice any problem, please notify me ASAP.
На английской версии страницы доступен changelog программы.
Convert unicode to windows
Type or paste text in the green box and click on the Convert button above it. Alternative representations will appear in all the other boxes. You can also do the same in any grey box, if you want to target only certain types of escaped text. You can then cut & paste the results into your document. selects all the text in a box (useful for deleting), and
copies the text to the clipboard, if your browser supports that. See the help page for other options.
Worked example. The text in the field with a green background currently contains a variety of escapes. Normally you would simply click on the Convert button just above the field to show the various escape formats below. Note, however, that this (unusually convoluted) text represents two characters using just a code point number – therefore, for this particular example, you should select Treat bare numbers as Hex code points (or decimal) to convert those numbers as well as the other escapes. You should also ensure that the checkbox next to Convert \n etc is selected, in order to convert the \n and \t escapes.
After selecting those options, click on the Convert button.
You will then see the conversion results in the grey boxes. You can use checkboxes alongside many boxes to tweak the results. For more information on each of the grey boxes, see the help page.
Конвертер текста в юникод
Конвертер для перевода любого текста (не только кириллицы) в Юникод. Набирайте текст — он будет автоматически преобразован по мере его набора. Либо вставьте текст из буфера и нажмите кнопку. Ограничение на длину текста — 3000 символов.
Что такое Юникод?
Юникод — это стандарт универсальной кодировки символов, который используется для поддержки символов, не входящих в набор ASCII. Изначально Интернет был создан на базе кодировки ASCII, которая содержит символы английского алфавита и состоит всего из 128 символов.
Юникод обеспечивает поддержку всех языков мира и их уникальных наборов символов — Юникод может поддерживать более 1 миллиона символов!
Причина в том, что в Юникоде для представления символа может использоваться больше бит (от английского binary digit — двоичное число), которые представляют собой единицы информации в компьютерах. Символы ASCII требуют только 7 бит, а Юникод может использовать 16 бит. Это необходимо, потому что для таких языков, как китайский, арабский и русский, требуется больше бит.
Есть несколько типов Юникода, самые распространенные — UTF-8 и UTF-16 . UTF-8 стал обычным стандартом в Интернете благодаря тому, что он позволяет регулировать количество бит в зависимости от символа. То есть символы ASCII в кодировке UTF-8 занимают только необходимое для них количество бит.
Кодовое пространство
Хотя формы записи UTF-8 и UTF-32 позволяют кодировать до 231 (2 147 483 648) кодовых позиций, было принято решение использовать лишь 220+216 (1 114 112) для совместимости с UTF-16 . Впрочем, даже и этого более чем достаточно — сегодня (в версии 5.0) используется чуть больше 99 000 кодовых позиций.
Кодовое пространство разбито на 17 плоскостей по 216 (65536) символов. Нулевая плоскость называется базовой, в ней расположены символы наиболее употребительных письменностей. Первая плоскость используется, в основном, для исторических письменностей. Плоскости 16 и 17 выделены для частного употребления.
Для обозначения символов Unicode используется запись вида « U+xxxx » (для кодов 0…FFFF) или « U+xxxxx » (для кодов 10000…FFFFF) или « U+xxxxxx » (для кодов 100000…10FFFF),
где xxx — шестнадцатеричные цифры.
Например, символ «я» (U+044F) имеет код 044F16 = 110310.
Состоит стандарт из двух главных разделов:
- универсального набора символов (англ. UCS)
- семейства кодировок (в английской интерпретации – UTF).
Универсальным набором символов задаётся однозначная пропорциональность кодам символов. Коды в этом случае представляют собой элементы кодовой сферы, являющиеся неотрицательными целыми числами. Функция семейства кодировок – определение машинного представления последовательности UCS-кодов.
В Юникод-стандарте коды градированы по нескольким областям. Ареал с кодами, начиная с U+0000 и заканчивая U+007F , – включает символы комплекта ASCII с необходимыми кодами. Дальше находятся области символов разных письменностей, символов технических, знаков пунктуации. Отдельную партию кодов хранят в резерве для будущего применения.
Под кириллицу определены следующие области символов с кодами:
C++ — Преобразования Unicode-кодировок с помощью STL-строк и Win32 API
Продукты и технологии:
C++ Unicode, Win32 API, STL-строки
В статье рассматриваются:
- кодировки UTF-8 и UTF-16;
- преобразование между UTF-8 и UTF-16 через Win32 API;
- обертывание MultiByteToWideChar в C++-функцию в современном стиле для преобразований UTF-8 в UTF-16;
- обработка ошибок с помощью C++-исключений.
Unicode является стандартом де-факто для представления текста на любых языках в современном программном обеспечении. Согласно веб-сайту официального консорциума Unicode (bit.ly/1Rtdulx), “Unicode предоставляет уникальное число для каждого символа независимо от платформы, программы и языка.” Каждое из этих уникальных чисел называют кодовой точкой (code point), и обычно она представляется с использованием префикса “U+”, за которым следует уникальное число в шестнадцатеричной форме. Так, кодовая точка, связанная с символом “C” — U+0043. Заметьте, что Unicode является отраслевым стандартом, охватывающим большинство систем письма в мире, включая иероглифы. Например, японская кана (ru.wikipedia.org/wiki/%D0%9A%D0%B0%D0%BD%D0%B0) содержит иероглиф 学 (среди других значений обозначающий обучение и знание), который связан с кодовой точкой U+5B66. В настоящее время в стандарте Unicode определены более 1 114 000 кодовых точек.
От абстрактных кодовых точек к реальным битам: кодировки UTF-8 и UTF-16
Кодовая точка — это абстрактная концепция. Для программиста вопрос заключается в том, как эти кодовые точки Unicode представляются в конкретном виде, используя биты? Ответ на этот вопрос ведет прямо к концепции Unicode-кодировки. По сути, Unicode-кодировка — конкретный, четко определенный способ представления значений кодовых точек Unicode в битах. В стандарте Unicode определено несколько кодировок, но наиболее важные из них — UTF-8 и UTF-16; обе они являются кодировками переменной длины (variable-length encodings), способными кодировать все возможные символы Unicode или, точнее, кодовые точки. Поэтому преобразования между этими двумя кодировками осуществляются без потерь: ни один символ Unicode не будет потерян в ходе этого процесса.
В UTF-8, как и предполагает название этой кодировки, используются восьмибитовые кодовые единицы (code units). Она была разработана с учетом двух важных характеристик. Во-первых, она обратно совместима с ASCII; это означает, что каждый допустимый ASCII-код символа имеет то же байтовое значение, что и при кодировании в UTF-8. Иначе говоря, допустимый ASCII-текст автоматически является допустимым текстом в кодировке UTF-8.
Во-вторых, поскольку Unicode-текст, закодированный в UTF-8, — это просто последовательность восьмибитовых байтов, исчезает проблема с порядком следования байтов (endianness). Кодировка UTF-8 (в отличие от UTF-16) по своей природе нейтральна к порядку следования байтов. Это важная особенность при обмене текстом между разными вычислительными системами, которые могут иметь разные аппаратные архитектуры с разным порядком следования байтов.
Если вспомнить о ранее упомянутых двух Unicode-символах, то заглавная буква “C” (кодовая точка U+0043) кодируется в UTF-8 одним байтом 0x43 (значение 43 шестнадцатеричное), который точно соответствует ASCII-коду, связанному с этим символом (поскольку UTF-8 обратно совместима с ASCII). Напротив, японский иероглиф 学 (кодовая точка U+5B66) кодируется в UTF-8 как последовательность из трех байтов: 0xE5 0xAD 0xA6.
UTF-8 — самая популярная Unicode-кодировка в Интернете. Согласно последним статистическим сведениям W3Techs, доступным по ссылке bit.ly/1UT5EBC, UTF-8 используется на 87% всех проанализированных веб-сайтов.
UTF-16 фактически является стандартом де-факто кодировки, используемой Windows API-функциями с поддержкой Unicode. UTF-16 — “родная” Unicode-кодировка и во многих других программных системах. Так, Qt, Java, библиотека International Components for Unicode (ICU) и прочие используют кодировку UTF-16 для хранения Unicode-строк.
В UTF-16 применяются 16-битные кодовые единицы. Как и UTF-8, UTF-16 позволяет кодировать все возможные в Unicode кодовые точки. Однако, если UTF-8 кодирует каждую допустимую в Unicode кодовую точку, используя от одного до четырех восьмибитовых байтов, UTF-16 в каком-то смысле проще. По сути, кодовые точки Unicode кодируются в UTF-16 всего одним или двумя 16-битными кодовыми единицами. Но наличие кодовых единиц размером больше одного байта влечет за собой проблемы с порядком следования байтов: фактически существует и “остроконечная” (big-endian) (от младшего байта к старшему) UTF-16, и “тупоконечная” (little-endian) (от старшего байта к младшему) UTF-16. Тогда как кодировка UTF-8 только одна, поскольку нейтральна к порядку следования байтов.
В Unicode определена концепция плоскости (plane) как непрерывной группы 65 536 (2 16 ) кодовых точек. Первая плоскость идентифицируется как плоскость 0, или Basic Multilingual Plane (BMP). Символы почти для всех современных языков и многие знаки находятся в BMP, а все эти BMP-символы представляются в UTF-16 одной 16-битной кодовой единицей.
Дополнительные символы (supplementary characters) расположены в плоскостях, отличных от BMP; они включают пиктографические символы вроде Emoji и символы вышедших из употребления письменностей наподобие египетских иероглифов. Эти дополнительные символы вне BMP кодируются в UTF-16 двумя 16-битными кодовыми единицами, также известными как суррогатные пары (surrogate pairs).
Заглавная буква “C” (U+0043) кодируется в UTF-16 как одна 16-битная кодовая единица 0x0043, а иероглиф 学 (U+5B66) — как одна 16-битная кодовая единица 0x5B66. Для многих Unicode-символов существует прямое соответствие между их абстрактным представлением в виде кодовой точки (скажем, U+5B66) и их шестнадцатеричной кодировкой в UTF-16 (например, 16-битное слово 0x5B66).
Чтобы слегка поразвлечься, давайте посмотрим на некоторые пиктографические символы. Unicode-символ “снеговик” (, U+2603) кодируется в UTF-8 как трехбайтовая последовательность: 0xE2 0x98 0x83; однако его эквивалент в UTF-16 является одной 16-битной кодовой единицей 0x2603. Unicode-символ “кружка пива” (
, U+1F37A), находящийся вне BMP, кодируется в UTF-8 как четырехбайтовая последовательность: 0xF0 0x9F 0x8D 0xBA. Его эквивалент в UTF-16 использует две 16-битные кодовые единицы, 0xD83C 0xDF7A, которые являются примером суррогатной пары в UTF-16.
Преобразования между UTF-8 и UTF-16 через Win32 API
Как обсуждалось выше, Unicode-текст представляется в памяти компьютера с использованием разных битов в зависимости от конкретной Unicode-кодировки. Какую кодировку вы должны использовать? Однозначного ответа на этот вопрос нет.
Сравнительно недавно стало общепринятым хранить Unicode-текст, закодированный в UTF-8, в экземплярах класса std::string в кросс-платформенном C++-коде. Более того, существует принципиальное соглашение о том, что UTF-8 является лучшей кодировкой для обмена текстом через границы приложений и разные аппаратные платформы. Тот факт, что UTF-8 нейтральна к порядку следования байтов, сыграл важную роль в этом. В любом случае преобразования между UTF-8 и UTF-16 необходимы по крайней мере на границе Win32 API, поскольку Windows-функции с поддержкой Unicode использую UTF-16 в качестве “родной” кодировки.
Теперь углубимся в код на C++, который реализует эти преобразования между Unicode-кодировками UTF-8 и UTF-16. Для этой цели можно использовать две ключевые Win32-функции: MultiByteToWideChar и симметричную ей WideCharToMultiByte. Первую вызывают для преобразования из UTF-8 (“многобайтовой” строки в специфической терминологии Win32 API) в UTF-16 (“широкобайтовую” строку), а вторую — для обратной задачи. Поскольку эти Win32-функции имеют сходные интерфейсы и шаблоны использования, в этой статье я сосредоточусь только на MultiByteToWideChar, но в сопутствующий компилируемый C++-код включена и другая функция.
Применение стандартных строковых STL-классов для хранения Unicode-текста Поскольку это статья по C++, справедливо ожидать хранения Unicode-текста в одном из строковых классов. Теперь возникает вопрос: какой вид строковых C++-классов можно использовать для хранения Unicode-текста? Ответ зависит от конкретной кодировки Unicode-текста. Если применяется кодировка UTF-8, то, поскольку она основана на восьмибитовых кодовых единицах, для представления каждой из этих единиц в C++ годится простой char. В этом случае STL-класс std::string, основанный на char, — хороший вариант для хранения Unicode-текста в кодировке UTF-8.
С другой стороны, если Unicode-текст закодирован в UTF-16, каждая кодовая единица представляется 16-битными словами. В Visual C++ тип wchar_t имеет как раз размер в 16 бит; соответственно STL-класс std::wstring, основанный на wchar_t, отлично подходит для хранения Unicode-текста в кодировке UTF-16.
Стоит отметить, что стандарт C++ не определяет размер типа wchar_t, поэтому, хотя компилятор Visual C++ использует для него 16 битов, другие компиляторы C++ могут оперировать другими размерами. И фактически размер wchar_t, определяемый компилятором GNU GCC C++ в Linux, составляет 32 бита. Так как тип wchar_t имеет разные размеры в разных компиляторах и платформах, класс std::wstring, основанный на этом типе, не является портируемым. Иначе говоря, wstring можно использовать для хранения Unicode-текста, закодированного в UTF-16, в Windows с компилятором Visual C++ (где размер wchar_t равен 16 битам), но не в Linux с компилятором GCC C++, который определяет 32-битный тип wchar_t.
На самом деле существует еще одна Unicode-кодировка, менее известная и реже используемая на практике, чем ее родственники: UTF-32. Как и предполагает ее название, она основана на 32-битных кодовых единицах. Поэтому 32-битный wchar_t из GCC/Linux — хороший кандидат для кодировки UTF-32 на платформе Linux.
Этой неоднозначностью размера wchar_t определяется отсутствие портируемости C++-кода, оперирующего с этим типом (включая сам класс std::wstring). С другой стороны, std::string, основанный на char, является портируемым. Но с практической точки зрения, следует отметить, что применение wstring для хранения текста в кодировке UTF-16 прекрасно подходит в C++-коде, специфичном для Windows. По сути, эти части кода уже взаимодействуют с Win32 API, которые специфичны для платформы просто по определению. Поэтому добавление wstring в смесь никак не меняет ситуацию.
Наконец, важно отметить, что из-за варьируемой длины в кодировках UTF-8 и UTF-16 возвращаемые значения методов string::length и wstring::length, как правило, не соответствуют количеству Unicode-символов (или кодовых точек), хранимых в строках.
Интерфейс функции преобразования Давайте разработаем функцию для преобразования Unicode-текст, закодированного в UTF-8, в эквивалентный текст с кодировкой UTF-16. Она может оказаться удобной, например когда у вас есть какой-то кросс-платформенный C++-код, который хранит Unicode-строки в кодировке UTF-8, используя STL-класс std::string, и вы хотите передавать этот текст в Win32-функции с поддержкой Unicode, обычно использующие кодировку UTF-16. Поскольку этот код взаимодействует с Win32 API, он уже не является портируемым, так что в данном случае прекрасно подходит std::wstring. Возможный прототип функции выглядит так:
Эта функция преобразования принимает в качестве ввода Unicode-строку в кодировке UTF-8, которая хранится в стандартном STL-классе std::string. Поскольку это входной параметр, он передается в функцию по const-ссылке (const &). После преобразования возвращается строка, закодированная в UTF-16, хранящаяся в экземпляре std::wstring. Однако при конвертации Unicode-кодировок вполне возможны проблемы. Например, входная строка UTF-8 может содержать недопустимую для UTF-8 последовательность (что может быть результатом ошибки в других частях кода или неких злонамеренных действий). В таких случаях самое лучшее с точки зрения безопасности — прекращать преобразование как неудавшееся вместо использования потенциально опасной последовательности байтов. Функция преобразования может обрабатывать случаи с недопустимыми входными последовательностями UTF-8, генерируя C++-исключение.
Определение класса исключения для ошибок преобразования Какого рода C++-класс можно применить для генерации исключения в случае неудачного преобразования Unicode-кодировки? Как вариант можно было бы использовать класс, уже определенный в стандартной библиотеке, например std::runtime_error. Однако я предпочитаю определить новый пользовательский C++-класс исключения для этой цели, унаследовав его от std::runtime_error. Когда Win32-функции вроде MultiByteToWideChar выдают ошибку, есть возможность вызывать GetLastError, чтобы получить подробную информацию о причине неудачи. Так, в случае недопустимых последовательностей UTF-8 во входной строке типичный код ошибки, возвращаемый GetLastErorr, — ERROR_NO_UNICODE_TRANSLATION. Имеет смысл добавить эту часть информации в пользовательский C++-класс исключения; она может оказаться полезной при отладке. Определение этого класса исключения начинается с:
Заметьте, что значение, возвращаемое GetLastError, имеет тип DWORD, который представляет 32-битное целое без знака. Однако DWORD является не портируемым typedef, специфичным для Win32. Но, даже если этот C++-класс исключения генерируется из Win32-специфичных частей C++-кода, он может быть захвачен кросс-платформенным C++-кодом! Поэтому имеет смысл использовать портируемые typedef вместо специфичных для Win32; uint32_t — пример такого типа.
Затем можно определить конструктор, который инициализирует экземпляры этого пользовательского класса исключения сообщением об ошибке и ее кодом:
Наконец, можно определить открытый аксессор get, обеспечивающий доступ к коду ошибки только для чтения:
Поскольку этот класс наследует от std::runtime_error, можно вызвать метод what, чтобы получить сообщение об ошибке, переданное в конструктор. Заметьте, что в определении этого класса используются лишь портируемые стандартные элементы, так что этот класс отлично работает в кросс-платформенных частях C++-кода, даже находящихся далеко от специфичной для Windows точки генерации.
Преобразование из UTF-8 в UTF-16: MultiByteToWideChar в действии
Теперь, когда определен прототип функции преобразования и реализован пользовательский C++-класс исключения для корректного представления ошибок преобразования UTF-8, пора создавать тело этой функции. Как и ожидалось, преобразование из UTF-8 в UTF-16 можно выполнить с помощью Win32-функции MultiByteToWideChar. Термины “многобайтовый” и “широкосимвольный” имеют исторические корни. Изначально эта API-функция и симметричная ей WideCharToMultiByte предназначались в основном для преобразования в Win32-функциях с поддержкой Unicode между текстом, хранящемся в специфических кодовых страницах, и Unicode-текстом, который использует кодировку UTF-16. Широкий символ относится к wchar_t, поэтому он связан со строкой на основе wchar_t, которая является строкой в кодировке UTF-16. Многобайтовая строка, напротив, — это последовательность байтов, выраженная в кодовой странице. Устаревшая концепция кодовой страницы была потом расширена для включения кодировки UTF-8.
Типичный шаблон использования этой API-функции заключается в том, что сначала вы вызываете MultiByteToWideChar, чтобы получить размер конечной строки. Затем создаете некий строковый буфер с этим размером. Обычно это делается вызовом метода std::wstring::resize в случае, если в нем будет храниться строка в кодировке UTF-16. (Подробнее об этом см. мою статью “Using STL Strings at Win32 API Boundaries” за июль 2015 года по ссылке msdn.com/magazine/mt238407.) Наконец, функция MultiByteToWideChar вызывается во второй раз для выполнения преобразования кодировки, используя ранее созданный строковый буфер. Заметьте, что тот же шаблон применяется и к симметричной API-функции WideCharToMultiByte.
Давайте реализуем этот шаблон в коде на C++, в теле пользовательской функции Utf8ToUtf16. Начнем с обработки особого случая с пустой входной строкой, где возвращается пустая выходная строка типа wstring:
Флаги преобразования MultiByteToWideChar можно вызвать в первый раз для получения размера конечной строки в UTF-16. Эта Win32-функция имеет сравнительно сложный интерфейс, и ее поведение определяется согласно флагам. Поскольку эта API-функция будет вызываться в теле функции преобразования Utf8ToUtf16 дважды, хорошая практика для надежности кода и удобства его сопровождения — определять именованную константу, которая может использоваться в обоих вызовах:
Безусловный провал преобразования, если во входной строке обнаруживается недопустимая для UTF-8 последовательность, является хорошей практикой и с точки зрения безопасности. Применение флага MB_ERR_INVALID_CHARS также приветствуется в книге Майкла Говарда (Michael Howard) и Дэвида Лебланка (David LeBlanc) “Writing Secure Code, Second Edition” (Microsoft Press, 2003).
Если ваш проект использует более старую версию компилятора Visual C++, которая не поддерживает ключевое слово constexpr, вы можете заменить его на static const в этом контексте.
Длины строк и безопасные преобразования из size_t в int MultiByteToWideChar ожидает, что параметр с длиной входной строки выражен типом int, тогда как метод length в строковых STL-классах возвращает значение типа, эквивалентного size_t. В 64-разрядных сборках компилятор Visual C++ генерирует предупреждение, указывающее на потенциальную опасность потери данных при преобразовании из size_t (размер 8 байтов) в int (размер 4 байта). Но даже в 32-разрядных сборках, где size_t и int определяются компилятором Visual C++ как 32-битные целые, имеется несовпадению “без знака/со знаком”: size_t — беззнаковый тип, а int — со знаком. Это не проблема для строк разумной длины, но для гигантских строк с размером более 2 31 –1, т. е. длиной свыше двух миллиардов байтов, преобразование из целого без знака (size_t) в целое со знаком (int) может генерировать отрицательное число, а отрицательные длины не имеют смысла.
Поэтому вместо простого вызова utf8.length для получения длины входной строки UTF-8 и передачи этого значения в MultiByteToWideChar лучше проверить реальный размер size_t, убедившись, что преобразование в int будет безопасным и имеющим смысл, и только потом передавать его в MultiByteToWideChar.
Следующий код позволяет проверить, что длина size_t не превысит максимальное значение для переменной типа int, и сгенерировать исключение, если такое превышение есть:
Обратите внимание на использование шаблона класса std::numeric_limits (из стандартного заголовочного файла
в C++) для запроса наибольшего возможного значения для типа int. Однако этот код на самом деле может не скомпилироваться. В чем дело? Проблема в определении макросов min и max в заголовочных файлах Windows Platform SDK. В частности, специфичное для Windows определение макроса max препроцессора конфликтует с вызовом функции-члена std::numeric_limits ::max. Предотвратить это можно несколькими способами.
Возможное решение — указать #define NOMINMAX до включения . Это исключит определение специфичных для Windows макросов препроцессора min и max. Однако отсутствие определения этих макросов на самом деле может вызвать проблемы с другими заголовочными файлами Windows такими как , в котором нужны определения этих специфичных для Windows макросов.
Поэтому другой вариант — использовать дополнительную пару скобок вокруг вызова функции-члена std::numeric_limits::max, чтобы исключить раскрытие вышеупомянутых макросов:
Более того, в качестве альтернативы можно было бы использовать константу INT_MAX вместо C++-шаблона класса std::numeric_limits.
Какой бы подход вы ни выбрали, после проверки размера и анализа того, что длина значения корректна для переменной типа int, вы можете безопасно привести тип size_t к int, используя static_cast:
Заметьте, что длина строки UTF-8 измеряется восьмибитовыми единицами символов (char units), т. е. в байтах.
Первый API-вызов: получение длины конечной строки Теперь MultiByteToWideChar можно вызвать в первый раз, чтобы получить длину конечной строки UTF-16:
Заметьте, что функция вызывается с передачей нуля в качестве последнего аргумента. Это инструктирует MultiByteToWideChar просто вернуть необходимый размер строки назначения; на этом этапе никакое преобразование не выполняется. Также обратите внимание на то, что размер строки назначения выражается в wchar_t (не в восьмибитовых символах); это имеет смысл, так как строка назначения является Unicode-строкой в кодировке UTF-16, состоящей из последовательностей 16-битных wchar_t.
Чтобы получить доступ только для чтения к содержимому входной строки UTF-8 типа std::string, вызывается метод std::string::data. Поскольку длина строки UTF-8 явным образом передается как входной параметр, этот код будет работать и для экземпляров std::string со встроенными NUL.
Константа CP_UTF8 используется, чтобы указать кодировку входной строки в UTF-8.
Обработка ошибок Если предыдущий вызов функции закончился неудачей, например из-за наличия недопустимых для UTF-8 последовательностей во входной строке, то MultiByteToWideChar возвращает 0. В этом случае можно вызвать Win32-функцию GetLastError, чтобы выяснить детали о причине сбоя. Типичный код ошибки, возвращаемый в случае недопустимых для UTF-8 символов, — ERROR_NO_UNICODE_TRANSLATION.
При неудаче следует сгенерировать исключение. Им может быть экземпляр ранее определенного пользовательского класса Utf8ConversionException:
Выделение памяти для строки назначения Если вызов Win32-функции выполняется успешно, необходимая длина строки назначения содержится в локальной переменной utf16Length, поэтому можно выделить память под выходную строку UTF-16. Для строк UTF-16, Хранящихся в экземплярах класса std::wstring, достаточно вызова метода resize:
Заметьте: поскольку длина входной строки UTF-8 была явно передана в MultiByteToWideChar (вместо передачи –1 и запроса к этой API-функции на сканирование всей входной строки, пока не встретится завершающий NUL-символ), она не станет добавлять дополнительный завершающий NUL-символ к конечной строке. Эта API-функция будет просто обрабатывать точное количество символов во входной строке, указанное явно переданным значением длины. Следовательно, нет нужды вызывать std::wstring::resize со значением “utf16Length + 1” : так как дополнительный завершающий NUL-символ не дописывается в Win32 API, вам незачем оставлять пространство для него в строке назначения типа std::wstring (подробнее об этом см. всю ту же статью за июль 2015 года).
Второй API-вызов: выполнение преобразования Теперь, когда экземпляр UTF-16 wstring имеет достаточно пространства для конечного текста в кодировке UTF-16, можно вызвать MultiByteToWideChar во второй раз, чтобы получить реально преобразованные биты в строке назначения:
Обратите внимание на использование синтаксиса “&utf16[0]” , чтобы получить доступ для записи к внутреннему буферу памяти std::wstring (это тоже уже обсуждалось в статье за июль 2015 года).
Если первый вызов MultiByteToWideChar был успешен, вряд ли второй вызов потерпит неудачу. Тем не менее, проверка возвращаемого API-функцией значения определенно является хорошей практикой безопасного кодирования:
Иначе (в случае успеха) конечную строку UTF-16 можно наконец вернуть вызвавшему:
Пример использования Итак, если у вас есть Unicode-строка в кодировке UTF-8 (скажем, полученная от какого-то кросс-платформенного C++-кода) и вы хотите передать ее в Win32-функцию с поддержкой Unicode, то наша функция преобразования может быть вызвана так:
Функция Utf8ToUtf16 возвращает экземпляр wstring, содержащий строку UTF-16, и применительно к этому экземпляру вызывается метод c_str, чтобы получить исходный указатель в стиле C на строку, завершаемую символом NUL, которая передается в Win32-функции с поддержкой Unicode.
Очень похожий код можно написать для обратного преобразования из UTF-16 в UTF-8, на этот раз вызывая API-функцию WideCharToMultiByte. Как уже отмечалось, преобразования в Unicode между UTF-8 и UTF-16 выполняются без потерь — ни один символ при преобразовании не теряется.
Библиотека преобразований Unicode-кодировок
В сопутствующий этой статье пакет исходного кода включен пример компилируемого C++-кода. Это повторно используемый код, без ошибок компилируемый в Visual C++ при уровне предупреждений 4 (/W4) в 32- и 64-разрядных сборках. Он реализован как библиотека C++ в виде только заголовочных файлов. По сути, этот модуль преобразования Unicode-кодировок состоит из двух заголовочных файлов: utf8except.h и utf8conv.h. Первый содержит определение C++-класса исключения, используемого для уведомления об ошибке при преобразованиях Unicode-кодировок. Второй реализует собственно функции преобразования Unicode-кодировок.
Заметьте, что utf8except.h содержит только кросс-платформенный C++-код; это делает возможным захват исключения при преобразовании кодировки UTF-8 в любых местах ваших проектов на C++, включая те части кода, которые не специфичны для Windows. Напротив, utf8conv.h содержит C++-код, специфичный для Windows, поскольку он напрямую взаимодействует с границей Win32 API.
Для повторного использования этого кода в ваших проектах просто включайте директивой #include эти заголовочные файлы. Сопутствующий пакет исходного кода содержит дополнительный файл, реализующий некоторые наборы тестов.
Заключение
Unicode является стандартом де-факто для представления текста на любых языках в современном программном обеспечении. Unicode-текст можно кодировать в разнообразных форматах: два наиболее важных из них — UTF-8 и UTF-16. В C++-коде для Windows часто требуется преобразовывать строки между кодировками UTF-8 и UTF-16, так как Win32-функции с поддержкой Unicode используют UTF-16 в качестве “родной” Unicode-кодировки. Текст в кодировке UTF-8 удобно хранить в экземплярах STL-класса std::string, тогда как std::wstring хорошо подходит для хранения текста в кодировке UTF-16 в C++-коде для Windows, ориентированном на компилятор Visual C++.
Win32-функции MultiByteToWideChar и WideCharToMultiByte позволяют выполнять преобразования Unicode-текста между кодировками UTF-8 и UTF-16. Я подробно описал шаблон использования функции MultiByteToWideChar, обернув ее в повторно используемую вспомогательную функцию на современном C++ для выполнения преобразований из UTF-8 в UTF-16. Обратное преобразование следует очень похожему шаблону, и повторно используемый C++-код, реализующий его, доступен в пакете кода, сопутствующем этой статье.
Джованни Диканио (Giovanni Dicanio) — программист со специализацией в области C++ и Windows, автор Pluralsight и обладатель звания Visual C++ MVP. Помимо программирования и создания учебных курсов, с удовольствием помогает другим на форумах и в сообществах, преданных C++. С ним можно связаться по адресу giovanni.dicanio@gmail.com. Также ведет блог на blogs.msmvps.com/gdicanio.
Выражаю благодарность за рецензирование статьи экспертам Дэвиду Крейви (David Cravey) и Марку Грегуа (Marc Gregoire).