Всем привет! Курю тут MSDN по работе с юникодом, и что-то не пойму, как завести функции транлсяции кодировок для работы в потоковом режиме. Наваял вот такой кодес: Код (C): #include <stdint.h> #include <malloc.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <winnls.h> #include <windows.h> enum status_codes { STATUS_OK, STATUS_UNSPECIFIED, STATUS_LOADING, STATUS_UNKNOWN_ERR, STATUS_NO_MEM, STATUS_NOT_FOUND, STATUS_BAD_FORMAT, STATUS_UNSUPPORTED_FORMAT, STATUS_CORRUPTED_FILE, STATUS_NO_DATA, STATUS_INVALID_UID, STATUS_DISCONNECTED, STATUS_BAD_ARGUMENTS, STATUS_NOT_BOUND, STATUS_BAD_STATE, STATUS_NOT_IMPLEMENTED, STATUS_ALREADY_EXISTS, STATUS_OVERFLOW, STATUS_BAD_HIERARCHY, STATUS_DUPLICATED, STATUS_TOO_BIG, STATUS_PERMISSION_DENIED, STATUS_IO_ERROR, STATUS_NO_FILE, STATUS_EOF, STATUS_CLOSED, STATUS_NOT_SUPPORTED, STATUS_INVALID_VALUE, STATUS_BAD_LOCALE, STATUS_NO_DEVICE, STATUS_OPENED, STATUS_BAD_TYPE, STATUS_TOTAL, STATUS_MAX = STATUS_TOTAL - 1, STATUS_SUCCESS = STATUS_OK }; typedef int status_t; #define CBUF_SIZE 0x20 #define WBUF_SIZE 0x20 status_t convert_to_utf16(const char *out, const char *in, UINT cp) { FILE *inf, *outf; if ((inf = fopen(in, "r")) == NULL) return STATUS_NOT_FOUND; if ((outf = fopen(out, "wb")) == NULL) { fclose(inf); return STATUS_PERMISSION_DENIED; } WCHAR *wbuf = reinterpret_cast<WCHAR *>(malloc(WBUF_SIZE * sizeof(WCHAR))); if (wbuf == NULL) { fclose(outf); fclose(inf); return STATUS_NO_MEM; } CHAR *cbuf = reinterpret_cast<CHAR *>(malloc(CBUF_SIZE * sizeof(CHAR))); if (cbuf == NULL) { free(wbuf); fclose(outf); fclose(inf); return STATUS_NO_MEM; } // Main cycle int isize = 0; status_t res = STATUS_OK; while (res == STATUS_OK) { // Read data size_t nbytes = fread(&cbuf[isize], sizeof(CHAR), CBUF_SIZE - isize, inf); if (nbytes == 0) { res = (feof(inf)) ? STATUS_OK : STATUS_IO_ERROR; break; } isize += nbytes; // Convert characters int n = MultiByteToWideChar(cp, 0, cbuf, isize, wbuf, WBUF_SIZE); if (n == 0) { switch (GetLastError()) { case ERROR_INSUFFICIENT_BUFFER: res = STATUS_NO_MEM; break; case ERROR_INVALID_FLAGS: case ERROR_INVALID_PARAMETER: res = STATUS_BAD_STATE; break; case ERROR_NO_UNICODE_TRANSLATION: res = STATUS_BAD_LOCALE; break; default: res = STATUS_UNKNOWN_ERR; break; } } else { // Write data nbytes = fwrite(wbuf, sizeof(WCHAR), n, outf); if (nbytes != size_t(n)) { res = STATUS_IO_ERROR; break; } // Move pointer (yes, this is dumb) CHAR *ebuf = cbuf; for (size_t i=n; i>0; --i) ebuf = CharNextExA(cp, ebuf, 0); nbytes = ebuf - cbuf; isize -= nbytes; if (isize > 0) memmove(cbuf, ebuf, isize); } } if ((res == STATUS_OK) && (isize != 0)) res = STATUS_CORRUPTED_FILE; free(cbuf); free(wbuf); fclose(outf); fclose(inf); return res; } Ну и вызываю функцию: Код (C): int main() { convert_to_utf16("out2-utf16.txt", "in2-utf8-ru.txt", CP_UTF8); return 0; } На выходе получается какое-то говно, в основном проблема из-за неправильного позиционирования тут: Код (C): // Move pointer (yes, this is dumb) CHAR *ebuf = cbuf; for (size_t i=n; i>0; --i) ebuf = CharNextExA(cp, ebuf, 0); Похоже, оно не понимает UTF-8 или как-то неправильно работает. Собственно, вопрос: как правильно находить место в cbuf, на котором функция MultiByteToWideChar прервала свою работу? А то в говнолинухе есть замечательная функция iconv, которая всё это учитывает и возвращает все необходимые для дальнейшей потоковой обработки значения, а вот в WinAPI что-то не могу найти.
то есть она просто падает с ошибкой и не возвращает, сколько символов были корректно переведены? можно посмотреть как mbstowcs реализована в венде, ну или просто ее заюзать... по идее она должна возвращать количество корректно переведенных символов до первой ошибки... --- Сообщение объединено, 27 дек 2018 --- почитай msdn про флаг MB_ERR_INVALID_CHARS... --- Сообщение объединено, 27 дек 2018 --- да и вообще, ты можешь эту функцию вызывать для каждого отдельного код пойнта... сначала вызываешь с нулл в роли буффера, чтобы определить длину утф-8 код пойнта, потом его конвертируешь... передвигаешь указатели в двух буфферах в зависимости от длины код пойнта...
Нет, она не падает. На выходе получаются "заеды" из-за того, что мы неправильно находим позицию в буфере cbuf, где остановилось. Получается вот такой текст на выходе: На однобайтных кодировках работает хорошо, а вот при скармливании UTF-8 получается такая хрень. Да это большой оверхед на вызов, хотелось как-то пачками данные обрабатывать.
В общем, решил пока через такой костыль: Код (C): status_t convert_to_utf16(const char *out, const char *in, UINT cp) { FILE *inf, *outf; if ((inf = fopen(in, "rb")) == NULL) return STATUS_NOT_FOUND; if ((outf = fopen(out, "wb")) == NULL) { fclose(inf); return STATUS_PERMISSION_DENIED; } WCHAR *wbuf = reinterpret_cast<WCHAR *>(malloc(WBUF_SIZE * sizeof(WCHAR))); if (wbuf == NULL) { fclose(outf); fclose(inf); return STATUS_NO_MEM; } CHAR *cbuf = reinterpret_cast<CHAR *>(malloc(CBUF_SIZE * sizeof(CHAR))); if (cbuf == NULL) { free(wbuf); fclose(outf); fclose(inf); return STATUS_NO_MEM; } // Main cycle ssize_t isize = 0; status_t res = STATUS_OK; while (res == STATUS_OK) { // Read data size_t nbytes = fread(&cbuf[isize], sizeof(CHAR), CBUF_SIZE - isize, inf); if (nbytes == 0) { res = (feof(inf)) ? STATUS_OK : STATUS_IO_ERROR; break; } isize += nbytes; // Convert characters ssize_t n = MultiByteToWideChar(cp, 0, cbuf, isize, wbuf, WBUF_SIZE); if (n == 0) { switch (GetLastError()) { case ERROR_INSUFFICIENT_BUFFER: res = STATUS_NO_MEM; break; case ERROR_INVALID_FLAGS: case ERROR_INVALID_PARAMETER: res = STATUS_BAD_STATE; break; case ERROR_NO_UNICODE_TRANSLATION: res = STATUS_BAD_LOCALE; break; default: res = STATUS_UNKNOWN_ERR; break; } } else { // If function meets invalid sequence, it replaces the codepoint with such magic value // We should know if function has failed if (wbuf[n-1] == 0xfffd) n--; // Write data nbytes = fwrite(wbuf, sizeof(WCHAR), n, outf); if (nbytes != size_t(n)) { res = STATUS_IO_ERROR; break; } // Estimate number of bytes decoded (yep, this is dumb but no way...) n = WideCharToMultiByte(cp, 0, wbuf, n, NULL, 0, 0, 0); // terminating 0 if ((n <= 0) || (n > isize)) { res = STATUS_IO_ERROR; break; } isize -= n; if (isize > 0) memmove(cbuf, &cbuf[n], isize); } } if ((res == STATUS_OK) && (isize != 0)) res = STATUS_CORRUPTED_FILE; free(cbuf); free(wbuf); fclose(outf); fclose(inf); return res; } Говно, конечно, но лучше варианта через нативные функции пока не наблюдаю.
меня канеш забавят такие вещи, когда вся функция написана по сути на сишечке без каких-либо намеков на плюсы, и вдруг по какой-то неведомой причине возникает реинтерпрет каст...
SadKo, ты читаешь и кодируешь блоками по 32 байта. А размер UTF-8 символа не фиксирован. Значит символ из 2+ байт может оказаться наполовину в одном блоке, наполовину - в другом. На выходе в таком случае как раз и будут такие зарубки.
Намедни тоже надо было сделать преобразовать форматов текста. Код (ASM): ;Таблица для преобразования ANSI CP1251 в UTF8 .const align 4 sCP1251_UTF8 db "Р‚",0,0, "Рѓ",0,0, "‚",0, "С“",0,0, "„",0, "…",0, "†",0, "‡",0 db "€",0, "‰",0, "Р‰",0,0, "‹",0, "РЉ",0,0, "РЊ",0,0, "Р‹",0,0, "РЏ",0,0 db "С’",0,0, "вЂ",0, "’",0, "“",0, "”",0, "•",0, "–",0, "—",0 db "В",0,0, "в„ў",0, "С™",0,0, "›",0, "Сљ",0,0, "Сњ",0,0, "С›",0,0, "Сџ",0,0 db "В ",0,0, "РЋ",0,0, "Сћ",0,0, "Р€",0,0, "В¤",0,0, "Тђ",0,0, "В¦",0,0, "В§",0,0 db "РЃ",0,0, "В©",0,0, "Р„",0,0, "В«",0,0, "В¬",0,0, "В",0,0, "В®",0,0, "Р‡",0,0 db "В°",0,0, "В±",0,0, "Р†",0,0, "С–",0,0, "Т‘",0,0, "Вµ",0,0, "В¶",0,0, "В·",0,0 db "С‘",0,0, "в„–",0, "С”",0,0, "В»",0,0, "С",0,0, "Р…",0,0, "С•",0,0, "С—",0,0 db "Рђ",0,0, "Р‘",0,0, "Р’",0,0, "Р“",0,0, "Р”",0,0, "Р•",0,0, "Р–",0,0, "Р—",0,0 db "Р",0,0, "Р™",0,0, "Рљ",0,0, "Р›",0,0, "Рњ",0,0, "Рќ",0,0, "Рћ",0,0, "Рџ",0,0 db "Р ",0,0, "РЎ",0,0, "Рў",0,0, "РЈ",0,0, "Р¤",0,0, "РҐ",0,0, "Р¦",0,0, "Р§",0,0 db "РЁ",0,0, "Р©",0,0, "РЄ",0,0, "Р«",0,0, "Р¬",0,0, "Р",0,0, "Р®",0,0, "РЇ",0,0 db "Р°",0,0, "Р±",0,0, "РІ",0,0, "Рі",0,0, "Рґ",0,0, "Рµ",0,0, "Р¶",0,0, "Р·",0,0 db "Рё",0,0, "Р№",0,0, "Рє",0,0, "Р»",0,0, "Рј",0,0, "РЅ",0,0, "Рѕ",0,0, "Рї",0,0 db "СЂ",0,0, "СЃ",0,0, "С‚",0,0, "Сѓ",0,0, "С„",0,0, "С…",0,0, "С†",0,0, "С‡",0,0 db "С€",0,0, "С‰",0,0, "СЉ",0,0, "С‹",0,0, "СЊ",0,0, "СЌ",0,0, "СЋ",0,0, "СЏ",0,0 .code align_proc TXT2HTML proc (dword) uses esi edi ebx pTxtUTF8:ptr, pTxtANSI:ptr mov esi, pTxtUTF8 mov edi, pTxtANSI ASSUME esi:ptr byte, edi:ptr byte .while (true) mov al, [edi] ;ANSI .if (al==10 || al==13) sprintf$(esi, &aBR) dec esi mov al, [edi+1] ;ANSI .if(al==10 || al==13) inc edi .endif .elseif (al>127) ;преобразовать ANSI CP1251 в UTF8 sub al, 128 movzx eax, al lea edx, sCP1251_UTF8[eax*4] .while (true) mov al, [edx] .break .if (al==0) mov [esi], al inc edx inc esi .endw inc edi .continue .else mov [esi], al .break .if (al==0) .endif inc edi inc esi .endw ASSUME esi:nothing, edi:nothing mov eax, esi ret TXT2HTML endp Функция предназначена для преобразования текста в HTML код и возвращает указатель на конец строки. Для UASM. Можно выкинуть не нужный код и оставить только преобразовать CP1251 в UTF8. Пример вызова. Код (ASM): mov pHTMLend, TXT2HTML(pHTMLend, &aBodyHTML_begin)
Проверено на русской и японской кодировках. Вы упустили, как раз, вот этот костыль: Код (C): // If function meets invalid sequence, it replaces the codepoint with such magic value // We should know if function has failed if (wbuf[n-1] == 0xfffd) n--; // Write data nbytes = fwrite(wbuf, sizeof(WCHAR), n, outf); if (nbytes != size_t(n)) { res = STATUS_IO_ERROR; break; } // Estimate number of bytes decoded (yep, this is dumb but no way...) n = WideCharToMultiByte(cp, 0, wbuf, n, NULL, 0, 0, 0); // terminating 0 if ((n <= 0) || (n > isize)) { res = STATUS_IO_ERROR; break; } isize -= n; if (isize > 0) memmove(cbuf, &cbuf[n], isize); Если последний символ не содержит полный валидный UTF8-код, то при декодировании он будет заменён на 0xfffd, мы детектим это и считаем, что декодировали на символ меньше, а недекодированный хвост помещаем в начало буфера, после чего дочитываем новые данные в конец.
Если вам больше нечего написать, то лучше не пишите, не засоряйте топик. Я кинул уже рабочий пример, протестированный на однобайтных кодировках, а также UTF-8 с двухбайтной и трёхбайтной длиной кодпоинта.
на самом деле можно просто по первым байтам определять длины utf8 символов и высчитывать, сколько символов полностью уложилось в буффер, переводить только полностью считанные кодпойнты... --- Сообщение объединено, 1 янв 2019 --- нашел пример на просторах интернета: Код (C): int32_t utf8_is_single_byte(char * c) { return (c[0] & 0x80) == 0x0; } int32_t utf8_is_double_byte(char * c) { return (c[0] & 0xe0) == 0xc0 && utf8_is_continuation(c[1]); } int32_t utf8_is_triple_byte(char * c) { return (c[0] & 0xf0) == 0xe0 && utf8_is_continuation(c[1]) && utf8_is_continuation(c[2]); } int32_t utf8_is_quadruple_byte(char * c) { return (c[0] & 0xf8) == 0xf0 && utf8_is_continuation(c[1]) && utf8_is_continuation(c[2]) && utf8_is_continuation(c[3]); } int32_t utf8_is_continuation(char c) { return (c & 0xc0) == 0x80; } size_t utf8_strlen(char * s) { size_t i = 0, len = 0; while(s[I]) [/I]{ if ( ! utf8_is_continuation(s)) ++len; ++i; } return len; }
Это верно, но задача состоит немного в другом: "Конвертация из произвольной CP в UTF-16", UTF-8 - всего-лишь частный случай. А для чисто UTF-8 есть вообще интересные решения на конечных автоматах: https://bjoern.hoehrmann.de/utf-8/decoder/dfa/
это верно, но тут надо знать один момент... функция MultiByteToWideChar не решает этой задачи, тк конвертирует только те кодовые страницы, которые установлены в системе... это минимум ACP, OEMCP и UTF-8, но остальные могут быть не установлены...
В принципе, согласен. Но вот этого перечня кодировок более чем достаточно: https://docs.microsoft.com/ru-ru/windows/desktop/Intl/code-page-identifiers В целом, этого достаточно. Моя задача - сконвертить из локальной кодировки (кодировка на целевой системе) в UTF-16 и назад, заведомо задетектив эту локальную кодировку. Поэтому всё будет работать так, как ожидается.
Не по теме, но "до кучи" Простой табличный конвертер CP1251-UTF8: https://github.com/gazlan/UTF-8-Cyrillic
Фу какой ты грубиян. Вот на STLных стримах (безо всяких обработок ошибок и прочего): Код (C++): #include <fstream> #include <codecvt> void convert(char const* in, char const* out) { std::wifstream input(in, std::ios::binary); std::wofstream output(out, std::ios::binary); input.imbue(std::locale(std::locale::empty(), new std::codecvt_utf8<wchar_t>)); output.imbue(std::locale(std::locale::empty(), new std::codecvt_utf16<wchar_t>)); while(!input.eof()) { output << static_cast<wchar_t>(input.get()); } } int main() { convert("C:/in.txt", "C:/out.txt"); } --- Сообщение объединено, 2 янв 2019 --- Тут еще момент в том, что wide char - не обязательно 2 байта, может быть и больше. Это тоже относится к "и прочего".
ну суррогатные пары MultiByteToWideChar вроде учитывает и не считает их одним символом... хотя я не сталкивался с какими-то проблемами с ними связанными...