
Как преобразовать кириллическую строку из UTF-8 в cp1251

Как преобразовать кириллическую строку из UTF-8 в cp1251
При разработке одной программы мне понадобилось преобразовать строки на русском языке из формата UTF-8 в формат cp1251. Внезапно выяснилось, что никакие средства WinAPI не позволяются выполнить эту операцию "одной строкой". Пришлось рассматривать даже варианты с табличным преобразованием, но потом нашлось более простое решение задачи. Алгоритм преобразования получился необычный, но зато гарантированно рабочий. Может быть это поможет сохранить время и нервы кому-нибудь еще.
Для начала вспомогательная функция, которая проверяет, является ли строка кириллической строкой в формате UTF-8. В ней должны быть только однобайтовые символы из первой половины таблицы ASCII и двухбайтовые символы, соответствующие русским буквам. Нулевой символ - признак окончания строки, длина строки значения не имеет.
Code (Assembler) : Убрать нумерацию
- ;--------------------------------------------------------
- ; Проверка строки на соответствие формату
- ; кириллического UTF-8
- ;--------------------------------------------------------
- ; Символы [0x00-0x7F] или двухсимвольные конструкции
- ; вида 0xD0[0x81|0x90-0xBF] или 0xD1[0x91|0x80-0x8F]
- ;--------------------------------------------------------
- ; На выходе:
- ; EAX = 1 - строка соответствует UTF-8
- ; EAX = 0 - строка не соответствует формату
- ;--------------------------------------------------------
- proc is_utf8 tstr:DWORD
- push esi ebx
- mov esi,[tstr]
- ; По умолчанию строка соответствует формату
- mov ebx,1
- .loc_scan:
- lodsb
- ; Окончание строки?
- or al,al
- jz .loc_ret
- ; Проверка символов [0x00-0x7F]
- cmp al,07Fh
- jbe .loc_scan
- ; Проверка двухсимвольной конструкции
- ; 0xD0[0x81|0x90-0xBF]
- cmp al,0D0h
- jne @f
- lodsb
- cmp al,81h
- je .loc_scan
- cmp al,90h
- jb .loc_fail
- cmp al,0BFh
- ja .loc_fail
- jmp .loc_scan
- @@:
- ; Проверка двухсимвольной конструкции
- ; 0xD1[0x91|0x80-0x8F]
- cmp al,0D1h
- jne .loc_fail
- lodsb
- cmp al,91h
- je .loc_scan
- cmp al,80h
- jb .loc_fail
- cmp al,8Fh
- jbe .loc_scan
- .loc_fail:
- ; Строка не соответствует формату
- xor ebx,ebx
- .loc_ret:
- mov eax,ebx
- pop ebx esi
- ret
- endp
Code (Assembler) : Убрать нумерацию
- ;--------------------------------------------------------
- ; Проверка строки на соответствие формату UTF-8
- ;--------------------------------------------------------
- ; На входе:
- ; lpStr - указатель на проверяемую строку
- ;--------------------------------------------------------
- ; На выходе:
- ; EAX = 1 - строка соответствует UTF-8
- ; EAX = 0 - строка не соответствует формату
- ;--------------------------------------------------------
- proc is_valid_utf8 lpStr:DWORD
- locals
- cp dd ?
- num dd ?
- endl
- push esi ebx ecx
- mov esi,[lpStr]
- ; По умолчанию строка соответствует формату
- mov ebx,1
- .loc_scan:
- lodsb
- ; Окончание строки?
- or al,al
- jz .loc_ret
- ; U+0000 to U+007F
- mov ah,al
- and ah,0x80
- or ah,ah
- jnz @f
- and al,0x7F
- movzx eax,al
- mov [cp],eax
- mov [num],1
- jmp .loc_check
- @@:
- ; U+0080 to U+07FF
- mov ah,al
- and ah,0xE0
- cmp ah,0xC0
- jne @f
- and al,0x1F
- movzx eax,al
- mov [cp],eax
- mov [num],2
- jmp .loc_check
- @@:
- ; U+0800 to U+FFFF
- mov ah,al
- and ah,0xF0
- cmp ah,0xE0
- jne @f
- and al,0x0F
- movzx eax,al
- mov [cp],eax
- mov [num],3
- jmp .loc_check
- @@:
- ; U+10000 to U+10FFFF
- mov ah,al
- and ah,0xF8
- cmp ah,0xF0
- jne @f
- and al,0x07
- movzx eax,al
- mov [cp],eax
- mov [num],4
- jmp .loc_check
- @@:
- jmp .loc_fail
- .loc_check:
- mov ecx,[num]
- @@:
- dec ecx
- or ecx,ecx
- jz @f
- lodsb
- mov ah,al
- and ah,0xC0
- cmp ah,0x80
- jne .loc_fail
- shl [cp],6
- and al,0x3F
- movzx eax,al
- or [cp],eax
- jmp @b
- @@:
- ; (cp > 0x10FFFF)
- cmp [cp],0x10FFFF
- ja .loc_fail
- ; (cp >= 0xD800) && (cp <= 0xDFFF)
- cmp [cp],0xD800
- jb @f
- cmp [cp],0xDFFF
- jbe .loc_fail
- @@:
- ; (cp <= 0x007F) && (num != 1)
- cmp [cp],0x007F
- ja @f
- cmp [num],1
- jne .loc_fail
- @@:
- ; (cp >= 0x0080) && (cp <= 0x07FF) && (num != 2)
- cmp [cp],0x0080
- jb @f
- cmp [cp],0x07FF
- ja @f
- cmp [num],2
- jne .loc_fail
- @@:
- ; (cp >= 0x0800) && (cp <= 0xFFFF) && (num != 3)
- cmp [cp],0x0800
- jb @f
- cmp [cp],0xFFFF
- ja @f
- cmp [num],3
- jne .loc_fail
- @@:
- ; (cp >= 0x10000) && (cp <= 0x1FFFFF) && (num != 4)
- cmp [cp],0x10000
- jb @f
- cmp [cp],0x1FFFFF
- ja @f
- cmp [num],4
- jne .loc_fail
- @@:
- jmp .loc_scan
- .loc_fail:
- ; Строка не соответствует формату
- xor ebx,ebx
- .loc_ret:
- mov eax,ebx
- pop ecx ebx esi
- ret
- endp
Code (Assembler) : Убрать нумерацию
- IS_TEXT_UNICODE_UNICODE_MASK = 0x000F
- ; Тестируемый формат строки
- mov [tmp],IS_TEXT_UNICODE_UNICODE_MASK
- invoke IsTextUnicode,test_string,string_len,tmp
- ; EAX = 1 - строка соответствует формату
- ; EAX = 0 - проверка не пройдена
Code (Assembler) : Убрать нумерацию
- ; Получить нужную длину строки для конвертирования
- invoke MultiByteToWideChar,CP_UTF8,0,str8,-1,0,0
- or eax,eax
- ; Конвертировать строку невозможно
- jz can_not_convert
- ; Промежуточное конвертирование UTF-8 -> UTF-16
- invoke MultiByteToWideChar,CP_UTF8,0,str8,-1,buff16,eax
- ; Получить нужную длину строки для конвертирования
- invoke WideCharToMultiByte,1251,0,buff16,-1,0,0,0,0
- or eax,eax
- ; Конвертировать строку невозможно
- jz can_not_convert
- ; Финальное конвертирование UTF-16 -> cp1251
- invoke WideCharToMultiByte,1251,0,buff16,-1,str1251,eax,0,0
- successfully_converted:
- ; str1251 = отконвертированная строка
- ...
- can_not_convert:
- ; Конвертировать строку невозможно
- ...
Корме описанных выше, в системе есть еще несколько функций для преобразования строк из одной кодировки в другую. Например, для преобразования из UTF-8 в UTF-16 можно воспользоваться следующим кодом. Тут используется недокументированная функция SHAnsiToUnicodeCP из библиотеки shlwapi.dll. По имени эта функция не экспортируется, надо получать ее адрес вручную по ординалу 216:
Code (Assembler) : Убрать нумерацию
- ; Загрузить библиотеку shlwapi.dll
- invoke LoadLibrary,szLib
- ; Получить адрес функции SHAnsiToUnicodeCP
- invoke GetProcAddress,eax,216
- stdcall eax,CP_UTF8,str8,buff16,255
Code (Assembler) : Убрать нумерацию
- struct ANSI_STRING
- Length dw ?
- MaximumLength dw ?
- Buffer dd ?
- ends
- struct UNICODE_STRING
- Length dw ?
- MaximumLength dw ?
- Buffer dd ?
- ends
- ansi ANSI_STRING
- unic UNICODE_STRING
- invoke RtlInitAnsiString,ansi,str1251
- invoke RtlInitUnicodeString,unic,str16
- mov [ansi.MaximumLength],255
- invoke RtlUnicodeStringToAnsiString,ansi,unic,FALSE
Code (Assembler) : Убрать нумерацию
- invoke SHUnicodeToAnsi,str16,str1251,100h
Code (Assembler) : Убрать нумерацию
- ; Загрузить библиотеку shlwapi.dll
- invoke LoadLibrary,szLib
- ; Получить адрес функции SHUnicodeToAnsiCP
- invoke GetProcAddress,eax,218
- stdcall eax,1251,str16,str1251,255
Просмотров: 10012 | Комментариев: 11
Метки: Assembler, полезные функции

Внимание! Статья опубликована больше года назад, информация могла устареть!
Комментарии
Отзывы посетителей сайта о статье
gudsan
(18.12.2024 в 22:15):
Спасибо!!! Полезная статья.

ManHunter
(20.05.2023 в 12:37):
Добавил примеры альтенативных функций для преобразования строк из одной кодировки в другую.

ManHunter
(20.09.2022 в 18:29):
Добавил более навороченную функцию проверки на соответствие строки формату UTF-8

ManHunter
(31.07.2016 в 17:44):
Примеры приведены. Берешь и используешь в своей программе.

Денис
(31.07.2016 в 12:04):
Как это можно использовать для перекодировки?

ManHunter
(03.01.2015 в 10:15):
*facepalm*
Igor K, не пиши больше ничего, не надо
Igor K, не пиши больше ничего, не надо

Igor K
(03.01.2015 в 09:24):
Перевод делается с помощью Notepad++

brute
(31.10.2014 в 07:11):
morgot, я имел в виду команды SSE и другие, которые за один такт работают со строками по 128бит (или более).

morgot
(29.10.2014 в 17:03):
brute, какой это "32 разрядный mov"? На 64 битах этой команды нет, по вашему? Про функцию вообще промолчу, могу сказать только то, что ваш образ мышления уж явно не Ассемблер.
ManHunter, спасибо, пригодится.
ManHunter, спасибо, пригодится.

ManHunter
(28.10.2014 в 10:35):
Все равно в нормальном софте придется делать обратную совместимость как минимум до WinXP

brute
(28.10.2014 в 06:34):
Круто! но я бы искал какую-нибудь сишную библиотеку, чтобы заинклудить нужную функцию. Она наверняка эффективнее, чем "старый" 32 разнрядный "mov". П.С. слышал, что в W7 и W8 появилось много новых api-функций, в том числе есть и новые строковые..

Добавить комментарий
Заполните форму для добавления комментария
