C ++ - преобразование wchar_t в сетевой байт и обратно

Основная причина в том, что я отправляю данные Unicode (байты, а не символы) через сокеты, и я хотел убедиться, что порядок байтов совпадает, потому что wchar_t - это UTF16.

Также принимающая программа - это моя другая программа, поэтому я буду знать, что это UTF16, и смогу соответствующим образом отреагировать.

Вот мой текущий алгоритм, который вроде работает, но дает странный результат. (Это в том же приложении, потому что я хотел узнать, как его преобразовать перед отправкой)

case WM_CREATE: {   


    //Convert String to NetworkByte
    wchar_t Data[] = L"This is a string";
    char* DataA = (char*)Data;
    unsigned short uData = htons((unsigned int)DataA);

    //Convert String to HostByte
    unsigned short hData = ntohs(uData);
    DataA = (char*)&hData;
    wchar_t* DataW = (wchar_t*)DataA;
    MessageBeep(0);


    break;
}

Результат:

쳌쳌쳌쳌쳌곭쳌쳌쳌쳌쳌ē쳌쳌쳌쳌This is a string

person Trevin Corkery    schedule 29.10.2016    source источник
comment
wchar - это не UTF16, имеет широкий характер прочтите это. Это может быть что угодно.   -  person Stargateur    schedule 29.10.2016
comment
Мое плохое, я должен неправильно прочитать ветку, в которой говорится о wchar_t / unicode / char. Но да, вы правы. (Я слышал, что по умолчанию в компиляторе MSVC используется UTF16)   -  person Trevin Corkery    schedule 29.10.2016
comment
Типовой каламбур в C ++ - UB. Я не думаю, что вам разрешено делать то, что вы делаете с DataA.   -  person Asu    schedule 29.10.2016
comment
@Asu Мне сказали, что если я хочу отправить Unicode через сокеты, я должен преобразовать его в байты, отправить по сети, а затем воссоздать строку, отбросив ее обратно. Если это плохой способ сделать это, есть ли лучший способ? Спасибо   -  person Trevin Corkery    schedule 29.10.2016
comment
Используйте MultiByteToWideChar и WideCharToMultiByte для преобразования между UTF16 (стандарт Windows) и UTF8 (дружественный к сети) Пример   -  person Barmak Shemirani    schedule 29.10.2016
comment
Можно здесь больше кода? Я не вижу, что вы пишете в сокете, что вы читаете. Пожалуйста, предоставьте минимальный образец.   -  person Stargateur    schedule 29.10.2016
comment
Вы приводите и конвертируете указатель на данные, а не на сами данные.   -  person Galik    schedule 29.10.2016
comment
Вы не воссоздаете массив. Вы переосмысливаете его адрес как массив символов. Отныне запись и чтение из него - это неопределенное поведение. Вот почему вы должны использовать static_cast большую часть времени вместо массивов в стиле C; они предотвращают такую ​​путаницу.   -  person Asu    schedule 29.10.2016
comment
@Stargateur Я обновил поток полной функцией, но я ничего не отправляю через сокет, я просто конвертирую его, а затем конвертирую обратно, чтобы узнать, как это сделать правильно, прежде чем я попытаюсь отправить его через сокет.   -  person Trevin Corkery    schedule 29.10.2016
comment
@BarmakShemirani В вашем примере: если бы у меня был тип пользователя 盘, преобразование его с помощью WideCharToMultiByte привело бы к нарушению 盘?   -  person Trevin Corkery    schedule 29.10.2016
comment
@TrevinCorkery L"盘" - это UTF16 wchar_t, он будет преобразован в UTF8 u8"盘" (хранится в char). Это один и тот же текст, но хранится по-разному. Сетевые функции ожидают UTF8   -  person Barmak Shemirani    schedule 29.10.2016


Ответы (2)


UTF8 и UTF16 хранят текст совершенно по-другому. Преобразование wchar_t* в char* бессмысленно, это то же самое, что преобразование float в char*.

Используйте WideCharToMultiByte для преобразования UTF16 в UTF8 для отправки в сетевую функцию.

При получении UTF8 от сетевых функций используйте MultiByteToWideChar для обратного преобразования в UTF16, чтобы его можно было использовать в функциях Windows.

Пример:

#include <iostream>
#include <string>
#include <windows.h>

std::string get_utf8(const std::wstring &wstr)
{
    if (wstr.empty()) return std::string();
    int sz = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], -1, 0, 0, 0, 0);
    std::string res(sz, 0);
    WideCharToMultiByte(CP_UTF8, 0, &wstr[0], -1, &res[0], sz, 0, 0);
    return res;
}

std::wstring get_utf16(const std::string &str)
{
    if (str.empty()) return std::wstring();
    int sz = MultiByteToWideChar(CP_UTF8, 0, &str[0], -1, 0, 0);
    std::wstring res(sz, 0);
    MultiByteToWideChar(CP_UTF8, 0, &str[0], -1, &res[0], sz);
    return res;
}

int main()
{
    std::wstring greek = L"ελληνικά";

    std::string utf8 = get_utf8(greek);
    //use utf8.data() for network function...

    //convert utf8 back to utf16 so it can be displayed in Windows:
    std::wstring utf16 = get_utf16(utf8);
    MessageBoxW(0, utf16.c_str(), 0, 0);

    return 0;
}


Изменить

Еще один пример, показывающий разницу между UTF16 и UTF8. В этом примере рассматриваются байтовые значения UTF16 и UTF8.

Обратите внимание, что для латинского алфавита байты UTF8 и ANSI абсолютно одинаковы.

Также для латинского алфавита есть сходство между UTF8 и UTF16, за исключением того, что в UTF16 есть лишний ноль.

Для греческого и китайского алфавита есть заметная разница.

//(Windows example)
void printbytes_char(const char* ANSI_or_UTF8)
{
    const char *bytes = ANSI_or_UTF8;
    int len = strlen(bytes);
    for (size_t i = 0; i < len; i++)
        printf("%02X ", 0xFF & bytes[i]);
    printf("\n");
}

void printbytes_wchar_t(const wchar_t* UTF16)
{
    //Note, in Windows wchar_t length is 2 bytes
    const char *bytes = (const char*)UTF16;
    int len = wcslen(UTF16) * 2;
    for (size_t i = 0; i < len; i++)
        printf("%02X ", 0xFF & bytes[i]);
    printf("\n");
}

int main()
{
    printbytes_char("ABC");
    printbytes_char(u8"ABC");
    printbytes_wchar_t(L"ABC");

    printbytes_char(u8"ελληνικά");
    printbytes_wchar_t(L"ελληνικά");

    printbytes_char(u8"汉字/漢字");
    printbytes_wchar_t(L"汉字/漢字");
    return 0;
}

Вывод:

"ABC":
41 42 43 //ANSI
41 42 43 //UTF8
41 00 42 00 43 00 //UTF16 (this is little endian, bytes are swapped)

"ελληνικά"
CE B5 CE BB CE BB CE B7 CE BD CE B9 CE BA CE AC //UTF8
B5 03 BB 03 BB 03 B7 03 BD 03 B9 03 BA 03 AC 03 //UTF16

"汉字/漢字"
E6 B1 89 E5 AD 97 2F E6 BC A2 E5 AD 97 //UTF8
49 6C 57 5B 2F 00 22 6F 57 5B //UTF16
person Barmak Shemirani    schedule 29.10.2016
comment
@Stargateur Да, это зависит от Windows. OP пометил winsock. Системы на основе Unix везде используют UTF8, поэтому им не нужно это неудобное преобразование. - person Barmak Shemirani; 29.10.2016
comment
Я думаю, это работает, потому что MessageBoxW обрабатывает юникод. Попробуйте использовать wprintf или std :: cout в консоли. MessageBoxW (Unicode) и MessageBoxA ( ANSI) - person Stargateur; 29.10.2016
comment
@Stargateur Windows имеет ограниченную поддержку Unicode для консоли Windows, это еще одна сложность. Для Windows API, такого как MessageBox, есть поддержка UTF16 (MessageBoxW) и поддержка ANSI (MessageBoxA). UTF8 и ANSI - это не одно и то же. Так уж получилось, что для латинского алфавита символы в ANSI и UTF8 совпадают. См. Обновленный ответ. Windows не может отображать строки UTF8 для нелатинского алфавита, вам понадобится компьютер на базе Linux, чтобы проверить это. - person Barmak Shemirani; 30.10.2016

person    schedule
comment
Нет, ты не можешь этого сделать. Актерский состав неправильный. Попробуйте использовать нелатинский язык и посмотрите, работает ли он. - person Barmak Shemirani; 29.10.2016
comment
@BarmakShemirani Вы уверены, потому что htons и ntohs должны работать со всеми типами до 32 бит. wchar_t не должны быть 32-битными? - person Stargateur; 29.10.2016
comment
@BarmakShemirani нет, мои решения ελληνικά обрабатываются UTF8 и, по-видимому, не поддерживаются широким характером. cpp.sh/4qx4ww - person Stargateur; 29.10.2016
comment
Обратите внимание, что в Windows длина wchar_t составляет 2 байта, а в системах на базе Linux wchar_t - 4 байта. - person Barmak Shemirani; 30.10.2016