Как преобразовать необработанные строки MBCS (SHIFT-JIS) из Windows в UTF-8 в Linux

Я пишу программу в Linux, которая должна взаимодействовать с существующей программой Windows. Я не могу изменить способ работы программы Windows, но я должен интегрироваться с существующими данными. Эта программа будет получать необработанные структуры данных через сетевой сокет TCP. К сожалению, программа Windows встраивает необработанные многобайтовые строки символов в структуры данных и не указывает, какая кодовая страница используется. Это работает нормально для английского языка, но плохо работает с языками, не основанными на латинице (например, с японским). В лучшем случае я могу предположить, какую кодовую страницу использует Windows. Если я работаю, и моя локаль установлена ​​​​на «ja» или «ja_JP», мне придется предположить, что машина с Windows использует кодовую страницу «SHIFT-JS» ... Уродливо, но это жизнь.

ВОПРОС:

Предполагая, что я правильно угадал кодовую страницу, как я могу преобразовать эти необработанные строки символов MBCS в строки UTF-8?

Вот пример необработанных данных:

Отправляемая строка: 私のクラスへようこそ

Данные MBCS, полученные от Windows (JP), (в байтах, добавлено дополнительное «0x00» для обеспечения нулевого завершения):

char kanji_win_raw_bytes[] =  { 0x8E, 0x84, 0x82, 0xCC, 0x83, 0x4E, 0x83, 0x89, 0x83, 0x58, 0x82, 0xD6, 0x82, 0xE6, 0x82, 0xA4, 0x82, 0xB1, 0x82, 0xBB, 0x00, 0x00, 0x00 };

Насколько я могу судить, строка поступает с компьютера с Windows, использующего кодовую страницу SHIFT-JS. Я пробовал mbsrtowcs():

const char *ptr = (char*)m_data;
// m_data contains the byte array of MBCS data
if ( m_data != NULL )
{
    std::mbstate_t state = std::mbstate_t();

    size_t bufflen = std::mbsrtowcs(NULL, &ptr, 0, &state);
    if ( bufflen == (size_t)-1 )
    {
        std::cout << "ERROR! mbsrtowcs() " << strerror(errno) << std::endl;
        std::cout << "Error at: " <<  (int32_t)( (char*)ptr - (char*)m_data ) << std::endl;
        return;
    }

    std::vector<wchar_t> wstr(bufflen);
    std::cout << "converting " << bufflen << " characters" << std::endl;
    std::mbsrtowcs(&wstr[0], &ptr, wstr.size(), &state);
    std::wcout << "Wide string: " << &wstr[0] << std::endl
        << "The length, including '\\0': " << wstr.size() << std::endl;
}

Вызов mbsrtowcs() завершается ошибкой в ​​позиции "0" без преобразования символов.

Затем я попробовал библиотеки iconv, используя кодовую страницу SHIFT-JS:

bytes_converted = 0;
char input[4096] = {0};
char dst[4096] = {0};
char* src = input;
size_t dstlen = sizeof(dst);
size_t srclen = 0;
iconv_t conv = iconv_open("UTF-8", "SHIFT-JIS" );

// make a copy
memcpy( (void*)input, (void*)kanji_win_raw_bytes, sizeof(kanji_win_raw_bytes) );
srclen = sizeof(kanji_win_raw_bytes);

if ( conv != (iconv_t)-1 )
{
    bytes_converted = iconv( conv, NULL, NULL, (char**)&dst, &dstlen );
    if ( bytes_converted == (size_t) -1 )
    {
        std::cerr << "ERROR: initializing output buffer: (" << errno << ") " << strerror(errno) << std::endl;
    }
    bytes_converted = iconv(conv, (char**)&src, &srclen, (char**)&dst, &dstlen);
    if ( bytes_converted == (size_t) - 1)
    {
        std::cerr << "ERROR in conversion: (" << errno << ") " << strerror(errno) << std::endl;
        if ( errno == EINVAL )
        {
                std::cerr << "RESULT: iconv() converted " << bytes_converted << " bytes: [" << dst << "]" << std::endl;
        }

    }
    else
    {
        std::cerr << "SUCCESS: iconv() converted " << bytes_converted << " bytes: [" << dst << "]" << std::endl;
    }
    iconv_close(conv);
}
else
{
    std::cerr << "ERROR: iconv_open() failed: " << strerror(errno) << std::endl;
}

Iconv segfaults (coredumps) с использованием заданной (японской) строки. Использовав iconv всего несколько раз, я считаю, что фрагменты кода (скопированные из онлайн-примеров) верны и, кажется, нормально работают с латинскими языками, используя аналогичную настройку, но разные (например, немецкие/французские) строки mbcs, поступающие с сервера Windows. .

Функции codecvt std::wstring_convert еще не реализованы в linux даже при компиляции с -std=c++11, так что это не вариант.

Заранее благодарим за любую помощь, которую вы можете предоставить.

-- Редактировать --

С помощью «myk» я создал пример приложения, которое лучше показывает мою проблему. С его предложениями мне удалось обойти ошибку сегментации, однако строка MBCS Windows не может быть преобразована независимо от выбранной локали.

/**
 * MBCS test
 */

    #include <stdlib.h>
    #include <unistd.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <sys/types.h>
    #include <string.h>
    #include <errno.h>

    #include <clocale>
    #include <string>
    #include <iostream>


    // 私のクラスへようこそ   (welcome to my class)
    const char* kanji_string = "私のクラスへようこそ";
    // This is what raw UTF-8 should look like
    uint8_t kanji_utf8_raw_bytes[] = { 0xE7, 0xA7, 0x81, 0xE3, 0x81, 0xAE, 0xE3, 0x82, 0xAF, 0xE3, 0x83, 0xA9, 0xE3, 0x82, 0xB9, 0xE3, 0x81, 0xB8, 0xE3, 0x82, 0x88, 0xE3, 0x81, 0x86, 0xE3, 0x81, 0x93, 0xE3, 0x81, 0x9D };

    // This is Windows MBCS using the SHIFT-JS code page
    uint8_t kanji_win_raw_bytes[] = { 0x8E, 0x84, 0x82, 0xCC, 0x83, 0x4E, 0x83, 0x89, 0x83, 0x58, 0x82, 0xD6, 0x82, 0xE6, 0x82, 0xA4, 0x82, 0xB1, 0x82, 0xBB, 0x00, 0x00, 0x00 };

    int main( int argc, char **argv )
    {
        std::setlocale(LC_ALL, "en_US.utf8");

        std::cout << "KANJI    String  [" << kanji_string << "]" << std::endl;  
        std::cout << "KANJI UTF-8 Raw  [" << kanji_utf8_raw_bytes << "]" << std::endl;  

        const char *data = (char*)kanji_win_raw_bytes;
        std::mbstate_t state = std::mbstate_t();
        size_t result = 0;

        wchar_t* buffer = (wchar_t*)malloc( sizeof(wchar_t) * (strlen((char*)data) + 1) );

        if ( buffer )
        {
            result = std::mbsrtowcs(buffer, &data, strlen(data), &state);
            if ( result == (size_t)-1 )
            {
                std::cout << "ERROR! mbsrtowcs() " << strerror(errno) << std::endl;
                std::cout << "Error at: " <<  (int32_t)( (char*)data - (char*)kanji_win_raw_bytes ) << std::endl;
            }
            else
            {
                std::wcout << "Wide string: [" << buffer << "] " << std::endl;
            }
            free( buffer );
        }

        return 0;
    }

Примечание: это можно скомпилировать и запустить на Linux/Mac с помощью следующей команды:

g++ mbcs_test.cpp -o mbcs_test && ./mbcs_test

person Zoccadoum    schedule 09.07.2014    source источник


Ответы (1)


Для mbsrtowcs() пара вещей:

1) Звонок:

size_t bufflen = std::mbsrtowcs(NULL, &ptr, 0, &state);

должно быть что-то вроде:

size_t bufflen = std::mbsrtowcs(buffer, &ptr, strlen(m_data), &state);

предполагая, что вы объявили «буфер» с чем-то вроде:

wchar_t* buffer = (wchar_t*) malloc(sizeof(wchar_t) * (strlen(m_data) + 1));

Третий параметр в mbsrtowcs(), который вы установили равным нулю, — это длина буфера результатов, по-видимому, поэтому преобразуется 0 символов.

2) По моему опыту, вам нужно использовать setlocale() для работы mbsrtowcs(). Я не вижу из фрагмента кода, но предлагаю вам включить что-то вроде:

#include <clocale>

:

std::setlocale(LC_ALL, "en_US.utf8");
person myk    schedule 17.07.2014
comment
Спасибо за предложения. Вы правы, я забыл включить вызов setlocale() в пример выше. Я обновил пример с вашими предложениями, и это решило проблему сбоя. По-прежнему существует проблема, заключающаяся в том, что mbsrtowcs по-прежнему не распознает строку Windows как допустимую строку MBCS. Он всегда возвращает неверный или неполный многобайтовый или расширенный символ в позиции 0. - person Zoccadoum; 09.10.2014